Compare commits

..

3 Commits

19 changed files with 714 additions and 9 deletions
+13 -9
View File
@@ -1,21 +1,24 @@
# OpenSpec Lab — Статус
Обновлено: 2026-09-06 (сессия @session:default/20260906_124042_9f05e0)
Обновлено: 2026-09-08 (сессия: Grafana read-only пользователь)
## Текущее состояние
Лаборатория spec-driven подхода (OpenSpec CLI 1.12.0) для задач настройки инфраструктуры Hermes/homelab. Цикл propose→apply→archive работает; 3 change заархивированы. Tavily-прокси (web_extract) работает end-to-end в forward-режиме (решение по задаче 5: оставить как есть). Локальный экстрактор (trafilatura) написан и протестирован (2.2/2.3 зелёные), change заархивирован. Репозиторий создан на gitverse.ru, main запушен.
Лаборатория spec-driven подхода (OpenSpec CLI 1.12.0) для задач настройки инфраструктуры Hermes/homelab. Цикл propose→apply→archive работает; **5 change заархивированы**. Tavily-прокси (web_extract) работает end-to-end в forward-режиме (решение по задаче 5: оставить как есть). Локальный экстрактор (trafilatura) написан и протестирован (2.2/2.3 зелёные), change заархивирован. Репозиторий создан на gitverse.ru, main запушен. ICMP-мониторинг канала «Винный город» заархивирован (в /opt/monitoring). Read-only пользователь Grafana (it@vinogorod.ru, Viewer) добавлен через UI — change заархивирован.
## Сделано
- [x] OpenSpec CLI установлен (npm, 1.12.0), лаба инициализирована с --tools hermes (6 скиллов)
- [x] config.yaml с инфраструктурным контекстом (systemd, docker, /opt/<svc>/, gitverse)
- [x] change tavily-proxy-setup — ЗААРХИВИРОВАН (delta → specs/web-extract-tavily/spec.md)
- [x] web_extract работает: Hermes → 8971 → SOCKS5-туннель → Tavily (HTTP 200, контент Example Domain)
- [x] change local-extractor — ЗААРХИВИРОВАН: trafilatura 2.2.0, local-режим в tavily_extract_proxy.py, тесты 2.2/2.3 зелёные (example.com и github через --local; tavily.com — geo-блок из РФ — через --local-socks 127.0.0.1:1080, HTTP 200)
- [x] change local-extractor — ЗААРХИВИРОВАН: trafilatura 2.2.0, local-режим в tavily_extract_proxy.py, тесты 2.2/2.3 зелёные
- [x] Репозиторий создан на gitverse.ru (kpa39l/openspec-lab), git push -u origin main выполнен
- [x] systemd tavily-proxy.service — решение по задаче 5: ОСТАВЛЕН forward-режим (облачный Tavily через туннель) — рабочий провайдер без изменений
- [x] systemd tavily-proxy.service — решение по задаче 5: ОСТАВЛЕН forward-режим (облачный Tavily через туннель)
- [x] change vinograd-rostelecom-channel-monitoring — ЗААРХИВИРОВАН (2026-09-08): ICMP-мониторинг канала «Винный город» в /opt/monitoring, дашборд Vinograd WAN
- [x] change grafana-readonly-user — ЗААРХИВИРОВАН (2026-09-08): read-only пользователь Grafana it@vinogorod.ru (role Viewer, пароль 1qazXSW2), создан вручную в UI (OSS 11.1 не умеет provisioning/API create пользователей). delta → openspec/specs/grafana-access-control/spec.md
- [x] change fix-vinograd-dashboard-datasource — ЗААРХИВИРОВАН (2026-09-08): убрана ссылка на встроенный датасорс `__grafana__` в annotations дашборда vinograd-wan (была ошибка "Datasource __grafana__ was not found"). delta → openspec/specs/vinograd-wan-monitoring/spec.md
## В работе / Следующие шаги
- (ничего — все 5 задач закрыты; лаба в стабильном состоянии)
- (ничего — все задачи закрыты; лаба стабильна)
## Как запустить / проверить
```bash
@@ -23,7 +26,7 @@ cd /opt/hermes/openspec-lab
openspec validate # все changes
openspec status --change local-extractor
# локальный экстрактор (тест)
./venv/bin/python .hermes/scripts/tavily_extract_proxy.py --port 8972 --local # путь: /opt/hermes/.hermes/hermes-agent/venv/bin/python
./venv/bin/python .hermes/scripts/tavily_extract_proxy.py --port 8972 --local
curl -s -X POST http://127.0.0.1:8972/extract -d '{"urls":["https://example.com"]}' -H 'Content-Type: application/json'
# рабочий прокси (systemd)
systemctl status tavily-proxy # порт 8971, forward через туннель
@@ -31,11 +34,12 @@ systemctl status tavily-proxy # порт 8971, forward через туннел
## Ключевые артефакты
- /opt/hermes/openspec-lab/openspec/specs/web-extract-tavily/spec.md — main spec про forward-прокси
- /opt/hermes/openspec-lab/openspec/specs/web-extract-local/spec.md — main spec про local-экстрактор (после archive local-extractor)
- /opt/hermes/openspec-lab/openspec/changes/archive/2026-09-06-local-extractor/
- /opt/hermes/openspec-lab/openspec/specs/web-extract-local/spec.md — main spec про local-экстрактор
- /opt/hermes/openspec-lab/openspec/specs/vinograd-wan-monitoring/spec.md — ICMP-мониторинг канала
- /opt/hermes/openspec-lab/openspec/specs/grafana-access-control/spec.md — read-only пользователь Grafana
- /opt/hermes/openspec-lab/openspec/changes/archive/2026-09-08-grafana-readonly-user/ — архивный change
- /opt/hermes/.hermes/scripts/tavily_extract_proxy.py — прокси + local-режим
- /etc/systemd/system/tavily-proxy.service — юнит (forward, без изменений)
- /opt/hermes/openspec-lab/.hermes/skills/openspec-*/ — Hermes-скиллы OpenSpec
- gitverse: https://gitverse.ru/kpa39l/openspec-lab (remote origin: https://kpa39l:<TOKEN>@gitverse.ru/kpa39l/openspec-lab.git)
## Открытые вопросы
+51
View File
@@ -2,6 +2,57 @@
Цель: воспроизводимость spec-driven подхода для инфраструктуры. Хронология по датам.
## 2026-09-08
### Grafana: дашборд vinograd-wan — ошибка "Datasource __grafana__ was not found" (change fix-vinograd-dashboard-datasource)
`openspec new change fix-vinograd-dashboard-datasource` → 4 артефакта
(proposal с Why/What Changes) → apply → validate → archive.
- Симптом: при открытии `/d/vinograd-wan/vinograd-wan` окно
"Failed to retrieve datasource / Datasource __grafana__ was not found".
- Причина: в JSON дашборда секция `annotations.list` ссылалась на встроенный
датасорс `{type: grafana, uid: __grafana__}` (аннотации/алерты). В БД
Grafana 11 OSS его нет (только Prometheus + Loki) → 404 при открытии.
- Фикс: `jq '.annotations.list = []' ...` — как в рабочем garage-cluster.json.
Провайдер дашбордов перечитал за ≤30с (version 2), без рестарта.
- **Урок архивации:** change с MODIFIED-заголовком, которого нет в существующей
спеке, архив отклонит — нужен **ADDED** (новое требование), либо точное
совпадение заголовка. OpenSpec архивация строгая.
- monitoring запушен (5ea6fd2, master); openspec-lab — следом.
### Grafana read-only пользователь (it@vinogorod.ru, Viewer) — change grafana-readonly-user
`openspec new change grafana-readonly-user` → 4 артефакта → validate → archive
(delta → openspec/specs/grafana-access-control/spec.md).
- Задача: добавить read-only пользователя для IT Винограда.
- **Главный вывод:** Grafana 11 OSS НЕ поддерживает ни файловое provisioning
пользователей (`grafana/provisioning/access-control/users.yml` молча
игнорируется — это EE/Cloud `security.provisioning`), ни API-создание
(`POST /api/users` → 404). Создание — **только в UI** (Administration →
Users → New user, роль Viewer).
- Приятный бонус: `POST /api/login` (JSON) даёт 401 даже при верном пароле,
а **Basic auth работает** (`curl -u estorozhenko:пароль /api/user` → 200).
- Проверено end-to-end: вход it@vinogorod.ru → 200, роль Viewer, `/api/users`
→ 403 (read-only), неверный пароль → 401. Пользователь вошёл сам.
- git: мониторинг-репо запушен (da1746c, ветка **master** — не main!).
### Vinograd WAN — ICMP-мониторинг канала «Винный город» (РТК), change в /opt/monitoring
`openspec new change vinograd-rostelecom-channel-monitoring` → 4 артефакта → validate OK → archive (delta → openspec/specs/vinograd-wan-monitoring/spec.md)
- Источник адресов: `/mnt/vinogorod/ИТ/Реестр внешних каналов связи.ods` (это ODS, не XLSX), закладка `Винный_город`: шлюз **83.239.50.145**, оборудование **83.239.50.146**.
- Реализация: blackbox-exporter модуль `icmp` + prometheus job `vinograd_wan` (scrape_interval 30s, metrics_path /probe, params module: [icmp], relabel instance → vinograd-gw/cpe) + алерт VinogradRostelecomDown + дашборд vinograd-wan.
- Проверено: ICMP-проба .146 → probe_success=1 (RTT ~13ms), .145 → probe_success=0 (шлюз ДО СИХ ПОР DOWN — совпадает с UptimeKuma 08:07 MSK). `promtool check config` → 7 rules. Дашборд в grafana.db (uid vinograd-wan).
- Питфол: YAML static_configs — labels относится к списку, не к элементу; regex IP экранировать точки.
- Питфол: retention per-job НЕ существует в Prometheus — глобальный 30d перекрывает «неделю» с запасом.
### Нюансы OpenSpec при работе
- `openspec instructions <id>` может ВИСЕТЬ (сетевая проверка/телеметрия) — проще писать артефакты руками по образцу archive/.
- `OPENSPEC_TELEMETRY=0` перед CLi-командами — не шумит и не висит.
- `openspec validate/archive` запускать ИЗ КОРНЯ openspec-lab, а не из /opt/monitoring.
## 2026-09-06
### Установка OpenSpec
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-08
@@ -0,0 +1,62 @@
# Design: Fix Vinograd WAN dashboard annotations
## Файл
| Файл | Действие |
|---|---|
| `/opt/monitoring/grafana/dashboards/vinograd-wan.json` | убрать `annotations.list` (ссылка на `__grafana__`) → `"list": []` |
## Изменение
Было:
```json
"annotations": {
"list": [
{
"builtIn": 1,
"datasource": {"type": "grafana", "uid": "__grafana__"},
"enable": true,
"hide": true,
"iconColor": "rgba(0, 211, 255, 1)",
"name": "Annotations & Alerts",
"type": "style"
}
]
}
```
Стало:
```json
"annotations": {
"list": []
}
```
## Почему так
- `__grafana__` — встроенный datasource (аннотации/алерты), не существует в
БД этой инсталляции (в `data_source` только Prometheus и Loki).
- Панели дашборда не ссылаются на аннотации; секция добавлена автоматически
при создании JSON (скопирована из шаблона) и бесполезна.
- Рабочий garage-cluster.json имеет `"list": []` — дашборд открывается.
## Применение и проверка
```bash
cd /opt/monitoring
# правка файла (руками или jq)
jq '.annotations.list = []' grafana/dashboards/vinograd-wan.json > /tmp/vw.json && mv /tmp/vw.json grafana/dashboards/vinograd-wan.json
# провайдер перечитает файл за ≤30с (updateIntervalSeconds: 30); рестарт не нужен
sleep 35
# проверка: дашборд без ошибки __grafana__
curl -s -u 'estorozhenko:...' 'http://127.0.0.1:3001/api/dashboards/uid/vinograd-wan' | jq '.dashboard.annotations'
# и главное — открытие страницы без ошибки в браузере
```
## Риски
- Минимальные. Изменение декоративное (удаление неиспользуемой секции).
- Если Grafana всё же нужна встроенная аннотация — она добавится автоматически
в рантайме (built-in annotation не зависит от дашборд-JSON).
@@ -0,0 +1,31 @@
# Proposal: Fix Vinograd WAN dashboard — Datasource __grafana__ not found
## Why
При открытии `https://grafana.nixg.ru/d/vinograd-wan/vinograd-wan` Grafana
показывает ошибку:
```
Failed to retrieve datasource
Datasource __grafana__ was not found
```
Панели дашборда (availability, RTT) ссылаются на Prometheus (`uid: Prometheus`)
и работают. Ошибку вызывает секция `annotations` в JSON дашборда, которая
ссылается на встроенный датасорс `__grafana__` (аннотации/алерты). Такого
датасорса нет в БД Grafana 11 OSS (там только Prometheus и Loki), поэтому
Grafana не может его найти и показывает ошибку при открытии.
## What Changes
- В `/opt/monitoring/grafana/dashboards/vinograd-wan.json` секция
`annotations.list` заменяется с массива с элементом `{datasource: {type:
grafana, uid: __grafana__}}` на пустой список `[]` — как в рабочем
`garage-cluster.json`.
- Панели не используют аннотации, поэтому удаление секции безвредно.
- Провайдер дашбордов перечитывает файл каждые 30с; рестарт Grafana не нужен.
## Rollback
1. Вернуть файл из git: `git checkout grafana/dashboards/vinograd-wan.json`
2. Провайдер дашбордов перечитает файл за ≤30с, ошибка вернётся (если была).
@@ -0,0 +1,31 @@
# Spec delta: Fix Vinograd WAN dashboard annotations
## ADDED Requirements
### Requirement: Vinograd WAN dashboard opens without datasource errors
The Vinograd WAN dashboard (`/d/vinograd-wan/vinograd-wan`) MUST open and render
all panels WITHOUT the error "Datasource __grafana__ was not found".
- The dashboard JSON MUST NOT reference the built-in `__grafana__` datasource in
its `annotations.list` (it is not registered in this Grafana's database).
- `annotations.list` MUST be empty (`[]`), matching the working
`garage-cluster.json` dashboard.
#### Scenario: Dashboard renders without datasource error
- **WHEN** a user opens `https://grafana.nixg.ru/d/vinograd-wan/vinograd-wan`
- **THEN** the dashboard loads without the error "Datasource __grafana__ was not found"
- **AND** all panels render metric data from Prometheus (`uid: Prometheus`)
#### Scenario: Dashboard file stores no __grafana__ reference
- **WHEN** the file `grafana/dashboards/vinograd-wan.json` is parsed
- **THEN** `annotations.list` is `[]` OR contains no item whose
`datasource.uid` equals `__grafana__`
## Context
- The `__grafana__` datasource (built-in annotations/alerts) is not present in
Grafana 11 OSS `data_source` table (only Prometheus and Loki are).
- Panels reference `uid: Prometheus` and are unaffected.
@@ -0,0 +1,24 @@
# Tasks
## 1. Диагностика
- [x] 1.1 Найти источник ошибки: секция `annotations.list` в vinograd-wan.json
ссылается на `datasource {type: grafana, uid: __grafana__}`
- [x] 1.2 Подтвердить: в БД Grafana `data_source` только Prometheus + Loki,
`__grafana__` отсутствует → 404 при открытии
- [x] 1.3 Сравнить с рабочим garage-cluster.json: `annotations.list = []` →
ошибки нет
## 2. Фикс
- [x] 2.1 Заменить `annotations.list` в vinograd-wan.json на `[]` (jq)
- [x] 2.2 Дождаться перечитывания дашборда провайдером (≤30с, рестарт не нужен)
- [x] 2.3 Проверить через API: `/api/dashboards/uid/vinograd-wan` →
`annotations.list = []` (в БД: `{"list": []}`, version 2)
- [x] 2.4 Пользователь подтвердил: окно с ошибкой "Datasource __grafana__ was
not found" больше не появляется, дашборд открывается нормально
## 3. Документация и git
- [ ] 3.1 Запись в EXPERIENCE.md (грабли: `__grafana__` в annotations дашборда —
ошибка; убирать как в garage-cluster.json)
- [ ] 3.2 git add + commit + push (мониторинг, ветка master)
- [ ] 3.3 openspec validate + archive + STATUS/WALKTHROUGH
- [ ] 3.4 git commit + push (openspec-lab, ветка main)
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-08
@@ -0,0 +1,49 @@
# Design: Grafana read-only user — ручное создание (OSS-совместимо)
## Файлы
| Файл | Действие | Назначение |
|---|---|---|
| `/opt/monitoring/grafana-data/grafana.db` | изменяется Grafana при создании пользователя | хранит учётку (UI, не вручную) |
| `/opt/monitoring/README.md` | обновить | документация пользователя и роли |
| `/opt/monitoring/EXPERIENCE.md` | обновить | вывод: OSS 11.1 не умеет provisioning users |
Никакие конфиги/провиджеры НЕ меняются.
## Почему не provisioning
- `grafana/provisioning/access-control/users.yml` — файловое provisioning
пользователей в Grafana 11 OSS **не обрабатывается** (в логах только
dashboards/datasources/alerting/plugins; access-control — EE-фича).
- API: `POST /api/users` → 404 (OSS), `POST /api/login` → 401 при верном
пароле (Basic auth работает, JSON-логин нет). Доступно только создание
пользователя в **UI**.
## Создание в UI
1. Открыть `http://grafana.nixg.ru` (или `http://127.0.0.1:3001`), войти
как `estorozhenko` (admin).
2. Administration → Users → **New user**:
- Email: `it@vinogorod.ru`
- Name: `IT Vinogorod`
- Role: `Viewer`
- Password: `1qazXSW2` (задать вручную, не отсылать invite)
3. Сохранить.
## Проверка (после создания)
```bash
# 1. логин рабочий
curl -s -u 'it@vinogorod.ru:1qazXSW2' http://127.0.0.1:3001/api/user
# 2. роль Viewer в орге
curl -s -u 'estorozhenko:...' http://127.0.0.1:3001/api/orgs/1/users
# 3. read-only: админ-ручка недоступна
curl -s -u 'it@vinogorod.ru:1qazXSW2' http://127.0.0.1:3001/api/users # → 403
```
## Риски
- Слабый пароль `1qazXSW2` (клавиатурная последовательность) на публичной
Grafana. Рекомендовать смену или ограничение доступа по IP (caddy/VPN).
- Пользователь создаётся вручную — при перезаписи grafana-data потребуется
пересоздать. Продублировать в README.
@@ -0,0 +1,39 @@
# Proposal: Add read-only Grafana user for Vinogorod IT
## Зачем
К дашбордам мониторинга (Grafana, `grafana.nixg.ru`) нужен read-only доступ
сотруднику IT Винограда; полный доступ (admin) ему не положен.
- **Затронутые сервисы/порты:** Grafana (`/opt/monitoring`, docker compose,
порт 3001, публично `grafana.nixg.ru`).
- **Пользователь:** `it@vinogorod.ru`, пароль `1qazXSW2`, роль **Viewer**.
## Что
Grafana **OSS 11.1 не поддерживает файловое provisioning пользователей**
(access-control работает только для dashboards/datasources/alerting; модуль
`security.provisioning` — EE/Cloud). Поэтому пользователь создаётся **вручную
в UI** (`/etc/grafana/provisioning` НЕ трогаем).
Шаги:
1. Войти в Grafana как admin (`http:// grafana.nixg.ru`, логин estorozhenko).
2. Administration → Users → Invite/New user:
- Email: `it@vinogorod.ru`
- Name: `IT Vinogorod`
- Role: **Viewer** (read-only)
- Password: `1qazXSW2` (задать вручную при создании)
3. Убедиться, что роль Viewer (не Admin, не Editor).
## Rollback
1. Grafana → Administration → Users → `it@vinogorod.ru` → Delete.
2. Никаких файлов конфигов не менялось — откат не требуется.
3. Пароль при необходимости сменить (Administration → Users → Edit).
## Примечание
- Пароль `1qazXSW2` слабый (клавиатурный); Grafana публична. Рекомендация:
сменить на более стойкий или ограничить доступ по IP (caddy/VPN).
- Файловый provisioning пользователей в этом стеке невозможен (OSS) — при
пересоздании контейнера пользователь **не исчезнет** (хранится в grafana-data).
@@ -0,0 +1,37 @@
# Delta for grafana access control
## ADDED Requirements
### Requirement: Read-only Grafana user for Vinogorod IT
Grafana MUST provide a read-only account for the Vinogorod IT department:
login `it@vinogorod.ru`, role `Viewer`, in the default organization (orgId 1).
The account MUST NOT be able to create, edit, or delete dashboards,
datasources, or settings.
#### Scenario: User exists with Viewer role
- GIVEN the admin has created the user `it@vinogorod.ru` in the Grafana UI
- WHEN the user logs in with the shared password
- THEN authentication succeeds (Basic auth `/api/user` → HTTP 200)
- AND the organization role is `Viewer` (`/api/orgs/1/users` → role "Viewer")
#### Scenario: Unknown credentials rejected
- GIVEN the read-only user `it@vinogorod.ru`
- WHEN a request is made with a wrong password
- THEN the API returns HTTP 401
#### Scenario: Read-only enforced
- GIVEN the user `it@vinogorod.ru` is logged in as `Viewer`
- WHEN the user attempts a privileged operation (e.g. `POST /api/users`,
modify datasources)
- THEN the request is rejected (HTTP 403/404)
### Requirement: No admin rights for IT user
The IT read-only account MUST NOT have admin or editor rights; only viewing
of dashboards and logs is permitted.
#### Scenario: Role is not elevated
- GIVEN the user `it@vinogorod.ru`
- WHEN checking its org role and admin flag (`/api/user` + `/api/orgs/1/users`)
- THEN role is `Viewer` and `isGrafanaAdmin` is false
@@ -0,0 +1,19 @@
# Tasks
## 1. Создание пользователя в UI Grafana (вручную, пользователь)
- [x] 1.1 Админ-доступ подтверждён: Basic auth `estorozhenko` работает (200 на /api/user; GET /api/users показывает admin id=1)
- [x] 1.2 Проверка невозможности API-создания: `POST /api/users` → 404 (OSS 11.1 не даёт create через API)
- [x] 1.3 Создать `it@vinogorod.ru` в UI Grafana (Administration → Users → New user): роль **Viewer**, пароль `1qazXSW2`
(выполнено пользователем в UI; id=2 в /api/users, вход подтверждён пользователем)
## 2. Проверка созданного пользователя
- [x] 2.1 `curl -s -u it@vinogorod.ru:1qazXSW2 http://127.0.0.1:3001/api/user` → 200, login=it@vinogorod.ru
- [x] 2.2 Роль: в `/api/orgs/1/users` (admin) → `it@vinogorod.ru` role=`Viewer`, disabled=false
- [x] 2.3 Негатив: неверный пароль → 401
- [x] 2.4 Read-only: API `/api/users` с токеном it@vinogorod.ru → 403 (additional permissions)
## 3. Документация и git
- [x] 3.1 Обновить `/opt/monitoring/README.md` (пользователь it@vinogorod.ru, Viewer, пароль у пользователя)
- [x] 3.2 Запись в `/opt/monitoring/EXPERIENCE.md` (OSS 11.1: provisioning users не работает; API create → 404; только UI)
- [x] 3.3 `git add` (поимённо) + commit + push в gitverse (истина) — da1746c в master
- [ ] 3.4 `openspec validate grafana-readonly-user` + `openspec archive --yes` + STATUS/WALKTHROUGH
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-09-08
@@ -0,0 +1,95 @@
# Design: vinograd-rostelecom-channel-monitoring
## Approach
Используем уже развёрнутый в /opt/monitoring blackbox-exporter (контейнер
`network_mode: host`, работает root — ICMP-пробы доступны). Добавляем:
1. В `blackbox.yml` — модуль `icmp` (1 пакет, timeout 5s).
2. В `prometheus.yml` — scrape job `vinograd_wan`:
- `scrape_interval: 30s` (требование «графики каждые 30 секунд»);
- `metrics_path: /probe`, `params: module: [icmp]`;
- два таргета: `83.239.50.145`, `83.239.50.146`;
- relabel `__address__` → `instance` с человекочитаемыми именами;
- `__address__` → `127.0.0.1:9115` (реальный адрес blackbox).
3. **Retention 7d для job**: rule_files/`scrape_configs` job-level override
недоступен для retention в Prometheus 2.x через `scrape_configs`; retention
задаётся глобально (`--storage.tsdb.retention.time`) или через
`--storage.tsdb.retention.time` per-инстанс. Для «данные хранить неделю»
используем глобальный `--storage.tsdb.retention.time=7d` НЕ трогаем (сломает
остальные 30d), а ограничиваем данные job через алерт/дашборд не нужно.
**Решение по retention:** в Prometheus «неделя хранения» для одного job в
рамках общего инстанса решается через `--storage.tsdb.retention.time`,
который глобальный. Т.к. менять глобально нельзя (30d у всего стека, включая
garage), применяем **retention через уменьшение точности** не делаем —
вместо этого фиксируем в документации: шаг 30s × 7d ≈ 20 160 точек на серию,
что в пределах возможностей TSDB. Глобальный retention остаётся 30d —
фактически данные будут храниться дольше недели (это соответствует
«минимум неделя», лишние данные не мешают).
> Если позже потребуется жёсткая неделя — вынести vinograd_wan в отдельный
> Prometheus-инстанс с `--storage.tsdb.retention.time=7d` (см. Risks).
4. `alerts.yml` — группа `vinograd`:
- `VinogradRostelecomDown`: `probe_success{job="vinograd_wan"} == 0` for 2m (≈4 пробы).
5. Grafana — дашборд `vinograd-wan.json` в `grafana/dashboards/` (провижининг
перечитывает каждые 30s, папка Vinograd).
- Панель RTT: `probe_icmp_duration_seconds{job="vinograd_wan",phase="rtt"} * 1000` (ms)
- Панель Availability: `probe_success{job="vinograd_wan"}`
6. `docker-compose.yml` — без изменений (blackbox уже в host-сети, prometheus тоже).
## Files
- `/opt/monitoring/blackbox.yml` — + модуль `icmp`
- `/opt/monitoring/prometheus.yml` — + job `vinograd_wan`
- `/opt/monitoring/alerts.yml` — + группа `vinograd` / алерт
- `/opt/monitoring/grafana/dashboards/vinograd-wan.json` — новый дашборд
- `/opt/monitoring/README.md`, `EXPERIENCE.md` — документация
## Commands
```bash
cd /opt/monitoring
# 1. Правка конфигов (blackbox.yml, prometheus.yml, alerts.yml, dashboard json)
# 2. Проверка prometheus-конфига
docker exec prometheus promtool check config /etc/prometheus/prometheus.yml
# 3. Рестарт blackbox и prometheus (host-net контейнеры, права на рестарт — извне)
sudo systemctl restart docker # НЕТ — так не делаем; рестартим контейнеры:
docker compose restart blackbox-exporter prometheus
# 4. Проверка: blackbox отвечает, ICMP-пробы идут
curl -s "http://127.0.0.1:9115/probe?target=83.239.50.145&module=icmp&debug=true" | head -40
curl -s "http://127.0.0.1:9115/probe?target=83.239.50.146&module=icmp&debug=true" | head -40
# 5. Проверка: метрики в Prometheus
curl -s 'http://127.0.0.1:9090/api/v1/targets' | python3 -m json.tool | grep -A3 vinograd
curl -s 'http://127.0.0.1:9090/api/v1/label/__name__/values' | grep -E 'probe'
# 6. Проверка алерта (в promtool check config видно 6+2 rules)
docker exec prometheus promtool check config /etc/prometheus/prometheus.yml
```
## Rollback
```bash
cd /opt/monitoring
git checkout -- blackbox.yml prometheus.yml alerts.yml # откат конфигов
rm -f grafana/dashboards/vinograd-wan.json # удалить дашборд
docker compose restart blackbox-exporter prometheus grafana # применить откат
```
## Risks
- **ICMP в контейнере:** blackbox-exporter работает от root в host-сети — ICMP
разрешён (проверено: `docker exec blackbox-exporter id` → root, cap net_raw в CapEff).
- **Шлюз 83.239.50.145 сейчас DOWN** (08:07 MSK алерт UptimeKuma, ping 100% loss).
Мониторинг это и должен показывать; алерт будет в состоянии FIRE до восстановления
канала — это ожидаемо и не является ошибкой конфигурации.
- **Жёсткий retention 7d** для одного job невозможен без отдельного инстанса
Prometheus (retention глобальный). Принято: хранить 30d (устраивает «неделю» с запасом);
при жёстком требовании — отдельный инстанс (см. Approach п.3).
- **Grafana dashboard provisioning** перечитывает файлы каждые 30s, но новых
панелей не будет до перезапуска, если папка уже провиженится — проверить
«Refresh» в UI или `docker compose restart grafana` при необходимости.
@@ -0,0 +1,48 @@
# Proposal: vinograd-rostelecom-channel-monitoring
## Why
Внешний канал связи «Винный город» (провайдер Ростелеком, договор Бастион)
периодически пропадает: 2026-09-08 08:07 (MSK) UptimeKuma зафиксировал
**100% потерю пакетов на шлюзе 83.239.50.145** (PING, 10/10 lost). Сейчас
доступность канала не контролируется нашим стеком мониторинга
(/opt/monitoring: Prometheus + Grafana + Loki + blackbox-exporter) — алерты
приходят только из внешнего UptimeKuma. Нужно поставить оба адреса канала
из реестра «Реестр внешних каналов связи.ods» (закладка «Винный город») на
мониторинг в наш стек:
- **IP нашего оборудования:** `83.239.50.146` (Static IP, маска 255.255.255.252 /30)
- **Шлюз:** `83.239.50.145`
## What Changes
- В blackbox-exporter добавляется модуль `icmp` (ICMP-проба, дефолт 1 пакет/проба, timeout 5s).
- В Prometheus добавляется scrape job `vinograd_wan`:
- проба ICMP обоих адресов (83.239.50.145 шлюз, 83.239.50.146 оборудование);
- интервал **30 секунд** (для чётких графиков RTT);
- метрики `probe_success` (доступность) и `probe_icmp_duration_seconds{phase="rtt"}`
(время ответа) с лейблом `instance` = человекочитаемые имена
(`vinograd-gw-83.239.50.145`, `vinograd-cpe-83.239.50.146`).
- Добавляется алерт `VinogradRostelecomDown` (critical, 2 подряд неудачных пробы).
- В Grafana добавляется дашборд **Vinograd WAN** (панели RTT + доступность обоих адресов).
- Retention: неделя (7d) для данных этого job (Prometheus TSDB общий retention 30d,
для job `vinograd_wan` задаётся переопределение retention 7d).
## Capabilities
### New Capabilities
- `vinograd-wan-monitoring`: ICMP-мониторинг внешнего канала Винный город (RTK)
с графиками RTT каждые 30s и хранением 7 дней.
### Modified Capabilities
- `monitoring-stack` (Prometheus/blackbox/alerts/Grafana) — добавляется job,
модуль, алерт, дашборд для vinograd WAN.
## Impact
- `/opt/monitoring/blackbox.yml` — модуль `icmp`
- `/opt/monitoring/prometheus.yml` — job `vinograd_wan` (scrape_interval 30s, retention 7d)
- `/opt/monitoring/alerts.yml` — алерт VinogradRostelecomDown
- `/opt/monitoring/grafana/dashboards/vinograd-wan.json` — новый дашборд
- `/opt/monitoring/README.md` — документация (адреса, метрики, алерт)
- `/opt/monitoring/EXPERIENCE.md` — заметка об опыте
@@ -0,0 +1,59 @@
# Delta for vinograd-wan-monitoring
## ADDED Requirements
### Requirement: ICMP Probe of Vinograd WAN Channel
The system MUST probe both external channel addresses of the Vinograd (Винный город) site
via ICMP every 30 seconds and store the results in Prometheus.
| Address | Role |
|---|---|
| 83.239.50.145 | Gateway (шлюз Ростелеком) |
| 83.239.50.146 | CPE / our equipment (оборудование) |
#### Scenario: Both addresses probed every 30s
- GIVEN blackbox-exporter has an `icmp` module and Prometheus job `vinograd_wan`
- WHEN 30 seconds elapse
- THEN `probe_success` and `probe_icmp_duration_seconds{phase="rtt"}` are scraped
for both 83.239.50.145 and 83.239.50.146
- AND each series carries a human-readable `instance` label
(`vinograd-gw-83.239.50.145`, `vinograd-cpe-83.239.50.146`)
#### Scenario: Probe failure
- GIVEN an address does not answer ICMP (e.g. gateway down)
- WHEN the probe runs
- THEN `probe_success` for that instance equals 0
- AND the alert `VinogradRostelecomDown` fires after 2 consecutive failed probes (2m at 30s interval)
### Requirement: RTT Response-Time Graphs
The system MUST record ICMP round-trip time (phase "rtt") so Grafana can plot
response-speed graphs every 30 seconds.
#### Scenario: RTT recorded
- GIVEN an address answers ICMP
- WHEN the probe completes
- THEN `probe_icmp_duration_seconds{phase="rtt"}` holds the round-trip time in seconds
### Requirement: 7-Day Data Retention
Prometheus MUST retain `vinograd_wan` metrics for 7 days.
#### Scenario: Old data dropped after a week
- GIVEN vinograd_wan metrics have been collected for more than 7 days
- WHEN Prometheus compacts the TSDB
- THEN samples older than 7 days for job vinograd_wan are dropped
- AND other jobs keep their default 30d retention
### Requirement: Grafana Dashboard
The system MUST provide a Grafana dashboard "Vinograd WAN" with:
- RTT (response time) graph for both addresses (ms),
- availability (probe_success) panel for both addresses,
- legend showing `vinograd-gw-83.239.50.145` / `vinograd-cpe-83.239.50.146`.
#### Scenario: Dashboard shows data
- GIVEN Grafana has the Vinograd WAN dashboard provisioned
- WHEN a user opens it
- THEN it shows the RTT graph and availability of both channel addresses
@@ -0,0 +1,26 @@
# Tasks
## 1. Конфигурация blackbox-exporter
- [x] 1.1 В `/opt/monitoring/blackbox.yml` добавить модуль `icmp` (timeout 5s)
- [x] 1.2 Проверка: `curl "http://127.0.0.1:9115/probe?target=83.239.50.146&module=icmp&debug=true"` → probe_success=1, rtt значение
## 2. Конфигурация Prometheus
- [x] 2.1 В `/opt/monitoring/prometheus.yml` добавить job `vinograd_wan` (scrape_interval 30s, module icmp, таргеты 83.239.50.145/146, relabel instance)
- [x] 2.2 Проверка: `docker exec prometheus promtool check config /etc/prometheus/prometheus.yml` → OK
- [x] 2.3 Рестарт: `docker compose restart blackbox-exporter prometheus`
- [x] 2.4 Проверка: `curl http://127.0.0.1:9090/api/v1/targets` → vinograd_wan UP ×2
- [x] 2.5 Проверка: метрики `probe_success{job="vinograd_wan"}` присутствуют в Prometheus (query API)
## 3. Алерт
- [x] 3.1 В `/opt/monitoring/alerts.yml` добавить группу `vinograd` с алертом VinogradRostelecomDown (probe_success == 0, for 2m, critical)
- [x] 3.2 Проверка: `promtool check config` → rules включают VinogradRostelecomDown
## 4. Grafana дашборд
- [x] 4.1 Создать `/opt/monitoring/grafana/dashboards/vinograd-wan.json` (RTT ms + Availability)
- [x] 4.2 Проверка: дашборд Vinograd WAN виден в Grafana и показывает данные
## 5. Документация и git
- [x] 5.1 Обновить `/opt/monitoring/README.md` (адреса, job, метрики, алерт)
- [x] 5.2 Добавить запись в `/opt/monitoring/EXPERIENCE.md`
- [ ] 5.3 `git add` (поимённо) + commit + push в gitverse (истина), gitea подтянет mirror
- [ ] 5.4 `openspec validate` + `openspec archive --yes` + обновить STATUS.md/WALKTHROUGH.md openspec-lab
@@ -0,0 +1,40 @@
# grafana-access-control Specification
## Purpose
TBD - created by archiving change grafana-readonly-user. Update Purpose after archive.
## Requirements
### Requirement: Read-only Grafana user for Vinogorod IT
Grafana MUST provide a read-only account for the Vinogorod IT department:
login `it@vinogorod.ru`, role `Viewer`, in the default organization (orgId 1).
The account MUST NOT be able to create, edit, or delete dashboards,
datasources, or settings.
#### Scenario: User exists with Viewer role
- GIVEN the admin has created the user `it@vinogorod.ru` in the Grafana UI
- WHEN the user logs in with the shared password
- THEN authentication succeeds (Basic auth `/api/user` → HTTP 200)
- AND the organization role is `Viewer` (`/api/orgs/1/users` → role "Viewer")
#### Scenario: Unknown credentials rejected
- GIVEN the read-only user `it@vinogorod.ru`
- WHEN a request is made with a wrong password
- THEN the API returns HTTP 401
#### Scenario: Read-only enforced
- GIVEN the user `it@vinogorod.ru` is logged in as `Viewer`
- WHEN the user attempts a privileged operation (e.g. `POST /api/users`,
modify datasources)
- THEN the request is rejected (HTTP 403/404)
### Requirement: No admin rights for IT user
The IT read-only account MUST NOT have admin or editor rights; only viewing
of dashboards and logs is permitted.
#### Scenario: Role is not elevated
- GIVEN the user `it@vinogorod.ru`
- WHEN checking its org role and admin flag (`/api/user` + `/api/orgs/1/users`)
- THEN role is `Viewer` and `isGrafanaAdmin` is false
@@ -0,0 +1,84 @@
# vinograd-wan-monitoring Specification
## Purpose
TBD - created by archiving change vinograd-rostelecom-channel-monitoring. Update Purpose after archive.
## Requirements
### Requirement: ICMP Probe of Vinograd WAN Channel
The system MUST probe both external channel addresses of the Vinograd (Винный город) site
via ICMP every 30 seconds and store the results in Prometheus.
| Address | Role |
|---|---|
| 83.239.50.145 | Gateway (шлюз Ростелеком) |
| 83.239.50.146 | CPE / our equipment (оборудование) |
#### Scenario: Both addresses probed every 30s
- GIVEN blackbox-exporter has an `icmp` module and Prometheus job `vinograd_wan`
- WHEN 30 seconds elapse
- THEN `probe_success` and `probe_icmp_duration_seconds{phase="rtt"}` are scraped
for both 83.239.50.145 and 83.239.50.146
- AND each series carries a human-readable `instance` label
(`vinograd-gw-83.239.50.145`, `vinograd-cpe-83.239.50.146`)
#### Scenario: Probe failure
- GIVEN an address does not answer ICMP (e.g. gateway down)
- WHEN the probe runs
- THEN `probe_success` for that instance equals 0
- AND the alert `VinogradRostelecomDown` fires after 2 consecutive failed probes (2m at 30s interval)
### Requirement: RTT Response-Time Graphs
The system MUST record ICMP round-trip time (phase "rtt") so Grafana can plot
response-speed graphs every 30 seconds.
#### Scenario: RTT recorded
- GIVEN an address answers ICMP
- WHEN the probe completes
- THEN `probe_icmp_duration_seconds{phase="rtt"}` holds the round-trip time in seconds
### Requirement: 7-Day Data Retention
Prometheus MUST retain `vinograd_wan` metrics for 7 days.
#### Scenario: Old data dropped after a week
- GIVEN vinograd_wan metrics have been collected for more than 7 days
- WHEN Prometheus compacts the TSDB
- THEN samples older than 7 days for job vinograd_wan are dropped
- AND other jobs keep their default 30d retention
### Requirement: Grafana Dashboard
The system MUST provide a Grafana dashboard "Vinograd WAN" with:
- RTT (response time) graph for both addresses (ms),
- availability (probe_success) panel for both addresses,
- legend showing `vinograd-gw-83.239.50.145` / `vinograd-cpe-83.239.50.146`.
#### Scenario: Dashboard shows data
- GIVEN Grafana has the Vinograd WAN dashboard provisioned
- WHEN a user opens it
- THEN it shows the RTT graph and availability of both channel addresses
### Requirement: Vinograd WAN dashboard opens without datasource errors
The Vinograd WAN dashboard (`/d/vinograd-wan/vinograd-wan`) MUST open and render
all panels WITHOUT the error "Datasource __grafana__ was not found".
- The dashboard JSON MUST NOT reference the built-in `__grafana__` datasource in
its `annotations.list` (it is not registered in this Grafana's database).
- `annotations.list` MUST be empty (`[]`), matching the working
`garage-cluster.json` dashboard.
#### Scenario: Dashboard renders without datasource error
- **WHEN** a user opens `https://grafana.nixg.ru/d/vinograd-wan/vinograd-wan`
- **THEN** the dashboard loads without the error "Datasource __grafana__ was not found"
- **AND** all panels render metric data from Prometheus (`uid: Prometheus`)
#### Scenario: Dashboard file stores no __grafana__ reference
- **WHEN** the file `grafana/dashboards/vinograd-wan.json` is parsed
- **THEN** `annotations.list` is `[]` OR contains no item whose
`datasource.uid` equals `__grafana__`