{"id":50146797,"url":"https://github.com/soneylegal/sentinel","last_synced_at":"2026-05-24T05:08:45.944Z","repository":{"id":355457152,"uuid":"1228038805","full_name":"soneylegal/sentinel","owner":"soneylegal","description":"Daemon autônomo e assíncrono para operações de Infraestrutura e DevOps.","archived":false,"fork":false,"pushed_at":"2026-05-10T23:28:34.000Z","size":102,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-11T01:24:17.941Z","etag":null,"topics":["daemon","devops","docker","python"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/soneylegal.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-03T14:09:58.000Z","updated_at":"2026-05-10T23:28:38.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/soneylegal/sentinel","commit_stats":null,"previous_names":["soneylegal/sentinel"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/soneylegal/sentinel","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soneylegal%2Fsentinel","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soneylegal%2Fsentinel/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soneylegal%2Fsentinel/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soneylegal%2Fsentinel/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/soneylegal","download_url":"https://codeload.github.com/soneylegal/sentinel/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/soneylegal%2Fsentinel/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33422101,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-23T22:14:44.296Z","status":"online","status_checked_at":"2026-05-24T02:00:06.296Z","response_time":57,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["daemon","devops","docker","python"],"created_at":"2026-05-24T05:08:27.920Z","updated_at":"2026-05-24T05:08:45.930Z","avatar_url":"https://github.com/soneylegal.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/soneylegal/sentinel/actions/workflows/ci.yml\"\u003e\u003cimg src=\"https://github.com/soneylegal/sentinel/actions/workflows/ci.yml/badge.svg?branch=main\" alt=\"CI\"/\u003e\u003c/a\u003e\n  \u003cimg src=\"https://img.shields.io/badge/python-3.11+-3776AB?style=for-the-badge\u0026logo=python\u0026logoColor=white\" alt=\"Python 3.11+\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/docker-socket-2496ED?style=for-the-badge\u0026logo=docker\u0026logoColor=white\" alt=\"Docker\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/FastAPI-009688?style=for-the-badge\u0026logo=fastapi\u0026logoColor=white\" alt=\"FastAPI\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/SQLite-003B57?style=for-the-badge\u0026logo=sqlite\u0026logoColor=white\" alt=\"SQLite\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/license-Apache%202.0-red?style=for-the-badge\" alt=\"License\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/code%20style-black-000000?style=for-the-badge\" alt=\"Code style: black\"/\u003e\n  \u003cimg src=\"https://img.shields.io/badge/type%20checked-mypy-blue?style=for-the-badge\" alt=\"mypy\"/\u003e\n\u003c/p\u003e\n\n\u003ch1 align=\"center\"\u003e🛡️ Sentinel\u003c/h1\u003e\n\u003ch3 align=\"center\"\u003eDocker Autonomous Orchestrator \u0026 Monitor\u003c/h3\u003e\n\n\u003cp align=\"center\"\u003e\n  Daemon assíncrono e autônomo para operações de Infraestrutura e DevOps.\u003cbr/\u003e\n  Monitora métricas via Docker Socket, executa ações corretivas automáticas\u003cbr/\u003e\n  e previne crash loops com um Circuit Breaker integrado.\n\u003c/p\u003e\n\n---\n\n## 📋 Índice\n\n- [Visão Geral](#-visão-geral)\n- [Quick Start (Apenas Docker)](#-quick-start-apenas-docker)\n- [Stack Tecnológico](#-stack-tecnológico)\n- [Arquitetura](#-arquitetura)\n- [Design Patterns](#-design-patterns)\n- [Estrutura de Diretórios](#-estrutura-de-diretórios)\n- [Instalação](#-instalação)\n- [Configuração](#-configuração)\n- [Uso](#-uso)\n- [API de Observabilidade](#-api-de-observabilidade)\n- [Testes](#-testes)\n- [Deploy com Docker Compose](#-deploy-com-docker-compose)\n- [Licença](#-licença)\n\n---\n\n## 🔭 Visão Geral\n\nO **Sentinel** é um daemon enterprise-grade que opera de forma autônoma sobre a sua infraestrutura Docker. Ele:\n\n- **Coleta métricas** (CPU, RAM, Health Status) de todos os containers em execução via Docker Socket — de forma totalmente assíncrona e non-blocking.\n- **Avalia regras** definidas em YAML contra as métricas coletadas, com suporte a duração sustentada (a condição precisa persistir por N segundos antes de agir).\n- **Executa ações corretivas** automáticas: restart, stop, scale (via `docker compose`).\n- **Previne Crash Loop BackOff** com um Circuit Breaker apoiado em SQLite — se um container for reiniciado mais de N vezes em M minutos, a ação autônoma é suspensa e humanos são alertados.\n- **Notifica** via múltiplos canais (Console, Discord, Slack) usando o padrão Strategy.\n- **Expõe uma API interna** para observabilidade do seu próprio estado.\n\n---\n\n## 🧱 Stack Tecnológico\n\n| Componente | Tecnologia | Propósito |\n|---|---|---|\n| **Linguagem** | Python 3.11+ | Tipagem forte via `mypy` strict |\n| **Docker** | `aiodocker` | Cliente assíncrono para o Docker Daemon |\n| **API** | FastAPI + Uvicorn | Servidor de observabilidade embutido |\n| **Banco de Dados** | SQLite + `aiosqlite` | Histórico de intervenções e estado do Circuit Breaker |\n| **Logging** | Loguru | Logs estruturados em JSON (Datadog/ELK-ready) |\n| **Configuração** | Pydantic Settings | Validação rigorosa de `.env` e `rules.yaml` |\n| **Notificações** | `aiohttp` | Webhooks assíncronos para Discord e Slack |\n| **Lint / Type Check** | `ruff` + `mypy` + `black` | Qualidade e formatação de código |\n| **Testes** | `pytest` + `pytest-asyncio` | 197 testes unitários e de integração |\n| **CI/CD** | GitHub Actions | Matrix Python 3.11/3.12/3.13 |\n\n---\n\n## 🏗️ Arquitetura\n\nO Sentinel segue os princípios de **Clean Architecture**, separando responsabilidades em módulos independentes e intercambiáveis.\n\n```\n┌─────────────────────────────────────────────────────────────────┐\n│                        main.py (Orchestrator)                    │\n│           Bootstraps + runs 3 concurrent asyncio tasks           │\n├──────────┬──────────────────────────────┬────────────────────────┤\n│          │                              │                        │\n│  ┌───────▼───────┐   ┌─────────────────▼──────────┐   ┌────────▼────────┐\n│  │  Collector     │   │     Rules Engine            │   │   FastAPI        │\n│  │  (aiodocker)   │──▶│  condition eval             │   │   /health        │\n│  │                │   │  sustained-duration tracker  │   │   /history       │\n│  └────────────────┘   │  circuit breaker check       │   │   /circuit-...   │\n│                       └──────┬──────────┬────────────┘   └─────────────────┘\n│                              │          │\n│                    ┌─────────▼──┐  ┌────▼──────────┐\n│                    │  Actions    │  │  Notifiers     │\n│                    │  (Strategy) │  │  (Strategy)    │\n│                    │  • Restart  │  │  • Console     │\n│                    │  • Stop     │  │  • Discord     │\n│                    │  • Scale    │  │  • Slack        │\n│                    └─────────┬──┘  └────────────────┘\n│                              │\n│                    ┌─────────▼──────────┐\n│                    │  State Manager     │\n│                    │  (SQLite)          │\n│                    │  • History         │\n│                    │  • Circuit Breaker │\n│                    └────────────────────┘\n└─────────────────────────────────────────────────────────────────┘\n```\n\n### Fluxo de Execução\n\n1. **Collector** consulta o Docker Daemon e normaliza métricas (compatível com cgroup v1/v2, Linux/macOS/WSL).\n2. **Rules Engine** cruza métricas com as regras configuradas.\n3. Se a condição for satisfeita pelo tempo sustentado, o engine consulta o **State Manager**.\n4. Se o **Circuit Breaker** estiver fechado, a **Action** é executada e uma **Notification** é enviada.\n5. Se o **Circuit Breaker** estiver aberto (muitos restarts recentes), a ação é suspensa e um alerta CRITICAL é emitido para intervenção humana.\n\n---\n\n## 🎯 Design Patterns\n\n### Strategy Pattern\nOs módulos `actions/` e `notifiers/` implementam interfaces abstratas (`BaseAction`, `BaseNotifier`). O engine invoca polimorficamente sem saber qual implementação concreta está em uso.\n\n```python\n# O engine não sabe se é Restart, Stop ou Scale\nawait action.execute(container_id, container_name, timeout)\n\n# O engine não sabe se é Console, Discord ou Slack\nawait notifier.send(title, message, severity, container_name)\n```\n\n### Observer Pattern\nO Rules Engine observa o fluxo de métricas do Collector de forma assíncrona a cada ciclo de polling, reagindo a mudanças de estado.\n\n### Circuit Breaker / State Pattern\nO State Manager mantém um registro persistente (SQLite) de todas as intervenções. Antes de executar qualquer ação destrutiva:\n\n```\n\"Eu já reiniciei esse container N vezes nos últimos M minutos?\"\n├── NÃO → Executa a ação normalmente\n└── SIM → Circuit Breaker ABERTO → Suspende ação → Alerta humanos\n```\n\n### Fail Fast\nA configuração (`.env` + `rules.yaml`) é validada rigorosamente via Pydantic **antes** do daemon inicializar. Regex inválido, métricas desconhecidas, ou campos obrigatórios ausentes impedem a inicialização.\n\n---\n\n## 📂 Estrutura de Diretórios\n\n```\nsentinel/\n├── src/\n│   ├── __init__.py\n│   ├── main.py                     # Orquestrador asyncio\n│   ├── core/\n│   │   ├── config.py               # Pydantic Settings + YAML Schema\n│   │   ├── logger.py               # Loguru JSON estruturado\n│   │   └── exceptions.py           # Exceções customizadas\n│   ├── collectors/\n│   │   └── docker_async.py         # aiodocker + normalização cross-platform\n│   ├── engine/\n│   │   ├── rules.py                # Motor de regras + sustained-duration\n│   │   └── state_manager.py        # SQLite + Circuit Breaker\n│   ├── actions/\n│   │   ├── base.py                 # Interface abstrata (Strategy)\n│   │   ├── restart.py              # RestartAction + StopAction\n│   │   └── scale.py                # ScaleComposeAction\n│   ├── notifiers/\n│   │   ├── base.py                 # Interface + ConsoleNotifier\n│   │   ├── discord.py              # Rich embeds via webhook\n│   │   └── slack.py                # Block Kit via webhook\n│   └── api/\n│       ├── server.py               # Uvicorn como asyncio task\n│       └── routes.py               # Endpoints de observabilidade\n├── tests/\n│   ├── conftest.py                 # Fixtures centralizadas + mocks\n│   ├── test_config.py              # Validação Pydantic (93 testes)\n│   ├── test_state_manager.py       # SQLite + Circuit Breaker (37 testes)\n│   ├── test_rules_engine.py        # Matching + Conditions (20 testes)\n│   └── test_api.py                 # Endpoints FastAPI (47 testes)\n├── .github/\n│   └── workflows/ci.yml            # GitHub Actions CI pipeline\n├── db/                             # Banco SQLite (criado em runtime)\n├── rules.yaml                      # Regras de monitoramento\n├── docker-compose.yml              # Deploy com socket mount\n├── Dockerfile                      # Multi-stage, non-root\n├── pyproject.toml                  # pytest + mypy + ruff + black\n├── requirements.txt                # Dependências\n├── .env.example                    # Template de configuração\n├── .gitignore\n└── LICENSE                         # Apache License 2.0\n```\n\n---\n\n## ⚡ Quick Start (Apenas Docker)\n\nPara rodar o Sentinel diretamente sem precisar clonar o repositório ou instalar dependências locais, utilize a nossa imagem pública hospedada no GitHub Container Registry:\n\n```bash\ndocker run -d \\\n  --name sentinel \\\n  --user root \\\n  --restart unless-stopped \\\n  -v /var/run/docker.sock:/var/run/docker.sock:ro \\\n  ghcr.io/soneylegal/sentinel:latest\n```\n\n\u003e **Nota:** Ao montar o `docker.sock`, o Sentinel ganha visibilidade global para orquestrar todos os containers do host, independentemente do diretório de onde é executado.\n\n---\n\n## 🚀 Instalação\n\n### Pré-requisitos\n\n- Python 3.11+\n- Docker Engine com socket acessível\n- (Opcional) Docker Compose v2\n\n### Setup local\n\n```bash\n# Clonar o repositório\ngit clone https://github.com/soneylegal/sentinel.git\ncd sentinel\n\n# Criar virtual environment\npython3 -m venv .venv\nsource .venv/bin/activate\n\n# Instalar projeto e dependências (modo editável)\npip install -e \".[dev]\"\n\n# Copiar e editar configuração\ncp .env.example .env\n```\n\n---\n\n## ⚙️ Configuração\n\n### Variáveis de Ambiente (`.env`)\n\n| Variável | Default | Descrição |\n|---|---|---|\n| `SENTINEL_DOCKER_URL` | `unix:///var/run/docker.sock` | URL do Docker Daemon |\n| `SENTINEL_API_HOST` | `0.0.0.0` | Host da API de observabilidade |\n| `SENTINEL_API_PORT` | `9120` | Porta da API |\n| `SENTINEL_RULES_PATH` | `rules.yaml` | Caminho do arquivo de regras |\n| `SENTINEL_DB_PATH` | `db/sentinel.db` | Caminho do banco SQLite |\n| `SENTINEL_POLL_INTERVAL` | `15` | Intervalo de coleta em segundos |\n| `SENTINEL_CIRCUIT_BREAKER_THRESHOLD` | `3` | Restarts antes de desarmar o disjuntor |\n| `SENTINEL_CIRCUIT_BREAKER_WINDOW_MINUTES` | `5` | Janela de tempo do disjuntor |\n| `SENTINEL_LOG_LEVEL` | `INFO` | Nível de log |\n| `SENTINEL_LOG_FORMAT` | `json` | Formato: `json` ou `pretty` |\n| `SENTINEL_DISCORD_WEBHOOK_URL` | — | Webhook do Discord |\n| `SENTINEL_SLACK_WEBHOOK_URL` | — | Webhook do Slack |\n\n### Regras de Monitoramento (`rules.yaml`)\n\nCada regra define:\n\n```yaml\nrules:\n  - name: \"Nome da Regra\"\n    description: \"Descrição\"\n    enabled: true\n    match:\n      container_name_pattern: \".*\"     # Regex: quais containers monitorar\n      exclude_patterns:\n        - \"^sentinel$\"                 # Regex: quais excluir\n    condition:\n      metric: cpu_percent              # cpu_percent | memory_percent | memory_usage_mb | health_status\n      operator: \"\u003e\"                    # \u003e | \u003c | \u003e= | \u003c= | ==\n      threshold: 90.0                  # Valor limite\n      sustained_seconds: 60           # Duração mínima da violação\n    action:\n      type: restart                    # restart | stop | scale | exec\n      timeout: 30                      # Timeout para ação graceful\n    notify:\n      channels:\n        - console                      # console | discord | slack\n      severity: critical               # info | warning | critical\n```\n\n#### Regras pré-configuradas\n\n| Regra | Condição | Ação |\n|---|---|---|\n| High CPU Auto-Restart | CPU \u003e 90% por 60s | Restart |\n| Memory Leak Detection | RAM \u003e 85% por 120s | Restart |\n| Unhealthy Container Watchdog | health_status == unhealthy por 30s | Restart |\n\n---\n\n## ▶️ Uso\n\n### Execução local\n\n```bash\n# Ativar venv\nsource .venv/bin/activate\n\n# Iniciar o daemon\npython -m src.main\n```\n\nO Sentinel irá:\n1. Validar toda a configuração (Fail Fast).\n2. Conectar-se ao Docker Daemon.\n3. Inicializar o banco SQLite.\n4. Iniciar a API de observabilidade na porta `9120`.\n5. Entrar no loop de monitoramento.\n\n### Parar o daemon\n\n```bash\n# Ctrl+C (SIGINT) ou\nkill -SIGTERM \u003cpid\u003e\n```\n\nO Sentinel faz shutdown graceful, fechando conexões e banco de dados.\n\n---\n\n## 📡 API de Observabilidade\n\nA API roda embutida no mesmo event loop do daemon (zero overhead de IPC).\n\n| Método | Endpoint | Descrição |\n|---|---|---|\n| `GET` | `/health` | Status + conexão Docker + uptime |\n| `GET` | `/history` | Últimas 50 intervenções autônomas |\n| `GET` | `/circuit-breakers` | Estado de todos os disjuntores |\n| `POST` | `/circuit-breakers/{name}/reset` | Reset manual de um disjuntor |\n| `GET` | `/docs` | Swagger UI interativo |\n| `GET` | `/redoc` | Documentação ReDoc |\n\n### Exemplos\n\n```bash\n# Verificar saúde do daemon\ncurl -s http://localhost:9120/health | python -m json.tool\n```\n```json\n{\n    \"status\": \"ok\",\n    \"docker_connected\": true,\n    \"uptime_seconds\": 3421.50,\n    \"version\": \"1.0.0\",\n    \"timestamp\": \"2026-05-07T18:30:00.000000+00:00\"\n}\n```\n\n```bash\n# Ver histórico de ações\ncurl -s http://localhost:9120/history | python -m json.tool\n```\n```json\n{\n    \"count\": 2,\n    \"records\": [\n        {\n            \"id\": 1,\n            \"container_id\": \"abc123def456\",\n            \"container_name\": \"webapp\",\n            \"rule_name\": \"High CPU Auto-Restart\",\n            \"action_type\": \"restart\",\n            \"success\": true,\n            \"error_message\": null,\n            \"created_at\": \"2026-05-07T18:25:00.000Z\"\n        },\n        {\n            \"id\": 2,\n            \"container_id\": \"def789abc012\",\n            \"container_name\": \"redis\",\n            \"rule_name\": \"Memory Leak Detection\",\n            \"action_type\": \"restart\",\n            \"success\": false,\n            \"error_message\": \"Container not found\",\n            \"created_at\": \"2026-05-07T18:20:00.000Z\"\n        }\n    ]\n}\n```\n\n```bash\n# Ver estado dos disjuntores\ncurl -s http://localhost:9120/circuit-breakers | python -m json.tool\n```\n```json\n{\n    \"breakers\": [\n        {\n            \"container_name\": \"webapp\",\n            \"trip_count\": 3,\n            \"last_tripped\": \"2026-05-07T18:25:00Z\",\n            \"is_open\": true\n        }\n    ]\n}\n```\n\n```bash\n# Resetar disjuntor manualmente\ncurl -s -X POST http://localhost:9120/circuit-breakers/webapp/reset | python -m json.tool\n```\n```json\n{\n    \"status\": \"ok\",\n    \"container_name\": \"webapp\",\n    \"message\": \"Circuit breaker for 'webapp' has been reset. Autonomous actions are now re-enabled.\"\n}\n```\n\n---\n\n## 🧪 Testes\n\n```bash\n# Rodar todos os testes\npython -m pytest tests/ -v\n\n# Lint + Type check\nruff check src/ tests/\nmypy src/ --strict\nblack --check src/ tests/\n\n# Resultado esperado:\n# tests/test_config.py             93 passed\n# tests/test_api.py                47 passed\n# tests/test_state_manager.py      37 passed\n# tests/test_rules_engine.py       20 passed\n# ==================== 197 passed in ~2.5s ====================\n```\n\n### Cobertura de testes\n\n| Módulo | Testes | O que valida |\n|---|---|---|\n| `test_config.py` | 93 | Pydantic settings, regex, YAML parsing, Fail Fast (22 cenários malformados) |\n| `test_api.py` | 47 | Todos os endpoints, schemas, 503 fallback, CORS, OpenAPI, 404/405 |\n| `test_state_manager.py` | 37 | SQLite CRUD, Circuit Breaker trip/reset, Crash Loop simulation, isolamento |\n| `test_rules_engine.py` | 20 | Pattern matching, operadores, sustained-duration, exclusões, circuit breaker |\n\n---\n\n## 🐳 Deploy com Docker Compose\n\n```bash\n# Build e start em background\ndocker compose up -d --build\n\n# Ver logs em tempo real\ndocker compose logs -f sentinel\n\n# Verificar saúde\ncurl http://localhost:9120/health\n\n# Parar\ndocker compose down\n```\n\n### O que o `docker-compose.yml` configura:\n\n- **Socket mount** (`/var/run/docker.sock`) em modo read-only.\n- **Volume persistente** para o banco SQLite.\n- **Healthcheck** contra o endpoint `/health`.\n- **Log rotation** (max 10MB, 3 arquivos).\n- **Non-root user** no container.\n- **Restart policy** `unless-stopped`.\n\n---\n\n## 📄 Licença\n\nEste projeto está licenciado sob a **Apache License 2.0** — veja o arquivo [LICENSE](LICENSE) para detalhes.\n\n```\nCopyright 2026 Davi Laurindo\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoneylegal%2Fsentinel","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsoneylegal%2Fsentinel","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsoneylegal%2Fsentinel/lists"}