diff --git a/README.md b/README.md index b71c0b5..77416eb 100644 --- a/README.md +++ b/README.md @@ -321,6 +321,11 @@ | `GHARD_PANEL_PORT` | `8000` | host-порт (docker-compose) | | `GHARD_OFFLINE_MULTIPLIER` | `3` | offline = нет пакетов дольше N × interval | | `GHARD_SHARE_INTERVAL` | `60` | период опроса сетевых хранилищ, сек | +| `GHARD_PUBLIC_IP` | *(пусто)* | ожидаемый публичный IP площадки с сервисами — для сверки публичного DNS; пусто — сверка не выполняется | +| `GHARD_PUBLIC_RESOLVER` | `1.1.1.1` | публичный резолвер для внешней проверки (в обход локального split-horizon DNS) | +| `GHARD_EXTERNAL_INTERVAL` | `60` | период внешней проверки сервисов, сек | +| `GHARD_EXTERNAL_TIMEOUT` | `5` | бюджет одной внешней пробы (DNS + TCP + TLS + строка статуса), сек | +| `GHARD_EXTERNAL_ALERT_AFTER` | `300` | столько секунд непрерывного отказа — и только тогда событие в Synapse | | `GHARD_SYNAPSE_URL` | *(пусто)* | URL Synapse (Gnexus Synapse Integration); пусто — отчёт о событиях отключён | | `GHARD_SYNAPSE_API_KEY` | *(пусто)* | ключ `syn_*` источника hard-panel (выдаётся один раз в админке Synapse) | | `GHARD_SYNAPSE_DEFAULT_SOURCE` | `hard-panel` | имя источника, зарегистрированное в Synapse | @@ -339,6 +344,8 @@ - `GET /api/v1/shares/{id}/samples?since=…` — история заполнения хранилища - `GET/POST/PATCH/DELETE /api/v1/services[/{id}]` — health-чеки сервисов (CRUD) - `GET /api/v1/services/{id}/samples?since=…` — история проб +- `GET /api/v1/services/{id}/external_samples?since=…` — история внешних + проверок публичного пути - `GET /api/v1/services/{id}/incidents?limit=50` — журнал моментов недоступности (периоды down+degraded из истории проб; у идущего периода `end=null`) @@ -371,6 +378,34 @@ чипами в карточке). Сырое тело не хранится; известные поля берутся по белому списку. +### Внешняя доступность + +У сервиса может быть необязательное поле **публичного URL** +(`https://…`, порт 443). Тогда панель дополнительно проверяет публичный путь: +публичный DNS (по умолчанию через 1.1.1.1, в обход локального split-horizon) → +ожидаемый публичный IP `GHARD_PUBLIC_IP` → TLS с SNI и полной проверкой +сертификата → `GET` публичного пути. Это ловит поломки, которых не видит +внутренний health-чек: DNS ведёт не на тот адрес (`dns_mismatch`), домена нет +(`dns_fail`), истёк/не тот сертификат (`tls_fail`), снаружи закрыт порт +(`down`), reverse proxy отдаёт 5xx или 421 (`http_fail`). Состояние `up` не +следует редиректам, а 3xx/4xx считаются доступностью — проверяется +достижимость публичного входа, а не health-семантика. Если сам резолвер +недоступен, точка помечается `probe_error` и **не** участвует в алертах. + +В списке и на странице сервиса рядом с внутренним статусом появляется второй +бейдж «снаружи: …» (подсказка — причина, адреса из DNS, срок сертификата); на +странице сервиса — отдельный график времени отклика публичного пути. +История внешних проверок — 7 дней. + +Непрерывный отказ дольше `GHARD_EXTERNAL_ALERT_AFTER` (по умолчанию 5 минут) +даёт ровно одно событие `service_external_down` в журнал и Synapse; после +восстановления — одно `service_external_recovered`, и только если down-событие +действительно отправлялось. Короткие моргания уведомлений не дают. Смена +публичного URL сбрасывает состояние и историю проверок (график не смешивает +два домена). Проба запускается с самой панели, поэтому внешний файрвол +*перед* панелью она не проверяет; для доменов за Cloudflare-прокси ожидаемый +IP не совпадёт с DNS — нужны DNS-only записи. + Карточки сервисов пытаются подтянуть мета с главной страницы origin'а (`` и favicon, обновление раз в 24 ч): если есть — отображается favicon и заголовок страницы вместо заданного имени (заданное имя остаётся @@ -383,8 +418,8 @@ журнал моментов недоступности — периоды бездействия `down` + `degraded` по истории проб со «сворачиванием» подряд идущих не-up точек (worst-состояние в периоде, код/причина из первой точки; идущий период — метка -«продолжается»). Там же — редактирование (имя/URL; смена URL даёт -немедленный повторный замер) и удаление сервиса. +«продолжается»). Там же — редактирование (имя, health-URL и публичный URL; +смена URL даёт немедленный повторный замер) и удаление сервиса. ## Журнал событий и интеграция с Synapse @@ -412,10 +447,15 @@ | `container_exited` | контейнер завершился (код в data) | warning | `container` × `exited` | normal | | `service_down` / `service_degraded` | первый замер вниз из up / деградация | critical / warning | `service` × `down` / `degraded` | high / normal | | `service_recovered` | сервис снова в порядке | info | `service` × `recovered` | low | +| `service_external_down` | публичный путь недоступен ≥5 мин (непрерывно) | warning | `service` × `external_down` | normal | +| `service_external_recovered` | публичный путь снова доступен (после отправленного down) | info | `service` × `recovered` | low | Повторяющиеся открытия (порог/офлайн/сервис — всё, что может «пылить») получают `dedup_key` вида `{type}-{id}-{UTC-день}` (дедупликация в Synapse, окно 24 ч); `ttl_seconds` = 24 ч — устаревший алерт не маршрутизируется. +Внешние события идут без `dedup_key`: одно событие на инцидент уже +гарантирует состояние `services.external_alert_open`, а дневной dedup +подавил бы второй реальный отказ за те же сутки. Конверты не содержат адресатов и каналов — «куда доставить», по конвенции экосистемы, решает Synapse по своим правилам. Приоритеты high/critical отправляются `send()` (ошибка видна в логе панели), остальные — @@ -487,7 +527,7 @@ | `panel_overview` | всё одной сводкой: серверы (статус, CPU/RAM/диск, сеть, последний пакет), сервисы (health), сетевые хранилища | | `server_details` | полное состояние сервера: метрики, диски, процессы, docker, заметка | | `server_history` | история метрик за N часов, прореженная до N точек (разряжение по возрасту, горизонт — год) | -| `services_overview` | все health-чеки: состояние, latency, код, self-report (версия/окружение/режим) | +| `services_overview` | все health-чеки: состояние, latency, код, self-report (версия/окружение/режим), внешний статус при заданном публичном URL | | `service_incidents` | журнал моментов недоступности сервиса (периоды down+degraded) | | `events_recent` | хвост единого журнала событий с фильтром по severity | @@ -497,7 +537,7 @@ | --- | --- | | `server_create` | регистрирует сервер и возвращает ключ агента `ghm_…` — **показан один раз**, агент обязан сохранить сразу | | `server_delete` | удаляет сервер необратимо: метрики и журнал стираются каскадом | -| `service_create` | добавляет health-чек; первая проба выполняется сразу | +| `service_create` | добавляет health-чек (необязательный `public_url` включает внешнюю проверку); первая проба выполняется сразу | | `service_delete` | удаляет сервис; история проб стирается | Читающие тулы — «наблюдательская выборка» («как мои серверы?», «что