From 7b711c2317c973ac01a3003ff42e606b2c2ad0ac Mon Sep 17 00:00:00 2001 From: Magnus Root Date: Wed, 25 Feb 2026 15:29:05 +0300 Subject: [PATCH] New readme --- ARCHITECTURE.md | 367 ++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 359 ++++++---------------------------------------- 2 files changed, 410 insertions(+), 316 deletions(-) create mode 100644 ARCHITECTURE.md diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md new file mode 100644 index 0000000..e78e590 --- /dev/null +++ b/ARCHITECTURE.md @@ -0,0 +1,367 @@ +# grafana_gen — архитектура и устройство утилиты + +Утилита автоматически генерирует Grafana-дашборды и правила алертинга для клиентов WAF на основе данных из PostgreSQL и шаблонов из Git-репозитория. + +--- + +## Обзор потока данных + +``` +PostgreSQL + sp_info + manual_info (UNION) + apps_settings + client_info + │ + ▼ + fetchClientsData() + map[clientTitle]ClientData + │ + ├──────────────────────────────────────┐ + ▼ ▼ + generateSingleDashboard() generateAndSendAlerts() + │ │ + ▼ ▼ + dashboard_template.json alert_rules_template.json + (Git-репозиторий) (Git-репозиторий) + │ │ + ▼ ▼ + POST /api/dashboards/db POST/PUT /api/v1/provisioning/alert-rules + (Grafana API) (Grafana Provisioning API, параллельно x5) + │ │ + ▼ ▼ + state.json ──── clients_hash state.json ──── alerts_hash + └─── alert_template_hash +``` + +--- + +## Структура файлов + +| Файл | Назначение | +|---|---| +| `main.go` | Точка входа, оркестрация всего потока | +| `config.go` | Константы, структуры `Config`, `ClientData`, `DomainInfo`, `DashboardTemplate` | +| `database.go` | Подключение к PostgreSQL, `fetchClientsData()` | +| `dashboard.go` | Генерация JSON дашборда из шаблона и данных клиентов | +| `panels.go` | Построение отдельных панелей, реестр query-режимов, `applyRPSThreshold()` | +| `alerts.go` | Генерация и отправка правил алертинга, параллельный upsert | +| `grafana.go` | HTTP-клиент для Grafana API (дашборды, папки) | +| `template.go` | Загрузка `dashboard_template.json` и `alert_rules_template.json` | +| `state.go` | Чтение/запись `state.json`, обнаружение изменений | +| `git.go` | `git clone` / `git pull` шаблонов из репозитория | +| `env.go` | Загрузка `.env` файла | +| `dashboard_template.json` | JSON-шаблон дашборда (Git-репозиторий) | +| `alert_rules_template.json` | JSON-шаблон алертов (Git-репозиторий) | + +--- + +## База данных + +### Источник данных + +Данные всегда берутся из **обеих** таблиц одновременно через `UNION`: + +```sql +SELECT ... FROM ( + SELECT sid, domain_name, aliases FROM sp_info WHERE sid IS NOT NULL + UNION + SELECT sid, domain_name, aliases FROM manual_info WHERE sid IS NOT NULL +) s +LEFT JOIN apps_settings a ON s.sid = a.l7resourceid +LEFT JOIN client_info ci ON a.client_title = ci.client_title +``` + +Дубликаты по SID схлопываются автоматически (`UNION` без `ALL`). + +### Таблицы + +**`sp_info` / `manual_info`** — список ресурсов: +- `sid` — идентификатор ресурса +- `domain_name` — основной домен +- `aliases` — JSON-массив дополнительных доменов + +**`apps_settings`** — маппинг SID → клиент: +- `l7resourceid` — SID ресурса +- `client_title` — название клиента (имя тенанта) + +**`client_info`** — настройки мониторинга клиента: +- `client_title` — название клиента +- `rps_limit` — порог RPS для алерта (NULL = алерт не создавать) +- `rps_commercial_limit` — коммерческий лимит RPS для синей линии на графике (NULL = 100) +- `four_hundred` — порог 4xx ошибок в % (NULL = алерт не создавать) +- `five_hundred` — порог 5xx ошибок в % (NULL = алерт не создавать) + +### Пример добавления нового клиента + +```sql +-- 1. Ресурс появится автоматически из sp_info + +-- 2. Привязать к клиенту +INSERT INTO apps_settings (l7resourceid, client_title) +VALUES ('SID12345', 'Название клиента'); + +-- 3. Задать лимиты для алертов (опционально) +INSERT INTO client_info (client_title, rps_limit, rps_commercial_limit, four_hundred, five_hundred) +VALUES ('Название клиента', 1800, 1200, 20, 10); +``` + +--- + +## Шаблоны + +Шаблоны хранятся в отдельном Git-репозитории и обновляются при каждом запуске через `git pull`. Изменение шаблона автоматически приводит к пересозданию дашборда и/или алертов. + +### dashboard_template.json + +Описывает структуру дашборда: метаданные, Grafana-переменные, строки (rows), панели. + +**Ключевые секции:** + +```json +{ + "version": "3.1.0", + "datasource": { "type": "...", "uid": "..." }, + "panels": { + "rps": { "query_mode": "bucket_logs", ... }, + "status_codes": { ... }, + "response_time": { ... }, + "traffic_combined": { ... } + }, + "layout": { + "client_panels": [...], + "overview_panels": [...], + "overview_row_title": "Обзор" + }, + "templating": { ... } +} +``` + +**Query-режимы панелей** (`query_mode`): + +| Режим | Описание | +|---|---| +| `bucket_logs` | Запрос к OpenSearch по одному SID | +| `multi_sid` | Запрос к OpenSearch по всем SID клиента | +| `combined_sids` | Объединённый запрос по SID + алиасам | +| `static` | Статические данные без запроса | + +**Обзорные панели** (`overview_panels`) используют Grafana-переменные (`$SID`, `$tenant`, `$node_name`) и отображаются в свёрнутой строке «Обзор» поверх клиентских строк. + +### alert_rules_template.json + +Описывает шаблон для алертов. + +**Структура:** + +```json +{ + "version": "1.3.0", + "defaults": { + "receiver": "Telegram PTAF Grafana", + "folder": "WAF - PTAF", + "group": "PTAF Grafana" + }, + "rps_alert": { + "relative_time_range_from": 1800, + "annotation_summary": "Превышен порог в {rps_limit} RPS в {client_title}" + }, + "static_alerts": [ ... ] +} +``` + +--- + +## Генерация дашборда + +Создаётся **один дашборд** со всеми клиентами. Структура дашборда: + +``` +┌─────────────────────────────────────────┐ +│ Grafana-переменные: tenant, SID, node │ +├─────────────────────────────────────────┤ +│ ▶ Обзор (свёрнутая строка) │ +│ overview_status_codes │ +│ overview_tenant_per_nodes │ +│ overview_tenant_per_node │ +│ overview_uri_top15 │ +├─────────────────────────────────────────┤ +│ ▼ Клиент A (развёрнутая строка) │ +│ RPS | Status codes | Response time │ +│ Traffic combined │ +│ [панели по доменам если > 1 домена] │ +├─────────────────────────────────────────┤ +│ ▶ Клиент B (свёрнутая строка) │ +│ ... │ +└─────────────────────────────────────────┘ +``` + +### Threshold на графике RPS + +На панель RPS автоматически накладываются цветные линии из `client_info`: + +| Линия | Значение | Источник | +|---|---|---| +| Синяя | `rps_commercial_limit` | `client_info.rps_commercial_limit` (NULL → 100) | +| Красная | `rps_limit` | `client_info.rps_limit` (0 → линия не рисуется) | + +--- + +## Алерты + +### Динамические алерты (per client) + +Создаются для каждого клиента у которого задан соответствующий лимит в `client_info`: + +| Тип | Условие | Структура запроса | +|---|---|---| +| RPS | `median(count/60) > rps_limit` | OpenSearch count → math /60 → reduce median → threshold | +| 4xx | `(errors/total)*100 > four_hundred` | 2x OpenSearch count → reduce → math % → threshold | +| 5xx | `(errors/total)*100 > five_hundred` | 2x OpenSearch count → reduce → math % → threshold | + +UID каждого алерта генерируется детерминированно: `md5(client_title + ":" + type)[:8]`. При повторном запуске алерт обновляется (`PUT`), а не создаётся заново. + +### Статические алерты (всегда присутствуют) + +Описаны в `alert_rules_template.json` в секции `static_alerts`, не зависят от БД: + +| Алерт | Условие | for | +|---|---|---| +| Use in / | диск `/` > 75% | 3m | +| Use in /var/log | диск `/var/log` > 75% | 5m | +| CPU Busy | CPU > 75% | 3m | +| RAM Busy | RAM > 75% | 3m | +| Состояние контейнеров | `docker_container_status == 0` | 2m | +| Упала Angie | `angie.service` не active | 1m | + +### Параллельная отправка + +Все алерты (динамические + статические) отправляются параллельно через горутины с ограничением **5 одновременных запросов** к Grafana API: + +``` +[RPS client1] [RPS client2] [4xx client1] [5xx client2] [static: CPU] ← 5 горутин + ↓ освободился слот + [static: RAM] ... +``` + +--- + +## State-файл + +Путь по умолчанию: `/var/lib/grafana_gen/state.json` + +Хранит хэши для обнаружения изменений между запусками: + +```json +{ + "last_run": "2026-02-25T10:00:00Z", + "template_commit": "a1b2c3d4...", + "template_version": "3.1.0", + "clients_hash": "sha256...", + "client_count": 42, + "domain_count": 87, + "sids": ["SID001", "SID002", "..."], + "alerts_hash": "sha256...", + "alert_template_hash": "sha256..." +} +``` + +**Логика запуска:** + +``` +изменился clients_hash → пересоздать дашборд +изменился template_commit → пересоздать дашборд +изменился template_version → пересоздать дашборд +изменился alerts_hash → переотправить алерты (без пересоздания дашборда) +изменился alert_template_hash → переотправить алерты (без пересоздания дашборда) +ничего не изменилось → выход без действий (если не указан -force) +``` + +--- + +## Конфигурация + +Приоритет разрешения параметров (от высшего к низшему): + +``` +1. Флаг командной строки (-grafana-url=...) +2. Переменная окружения (GRAFANA_URL=...) +3. .env файл (/etc/grafana_gen/grafana_gen.env) +4. Константа в config.go (DefaultGrafanaURL) +``` + +### Пути к .env файлу (перебираются по порядку) + +1. `$GRAFANA_GEN_ENV_FILE` (переменная окружения) +2. `/etc/grafana_gen/grafana_gen.env` +3. `./grafana_gen.env` +4. `./.env` + +### Пример .env файла + +```env +# База данных +DB_USER=grafana_reader +DB_PASSWORD=secret + +# Grafana +GRAFANA_URL=https://grafana.example.com +GRAFANA_API_KEY=glsa-xxxxxxxxxxxxxxxxxxxx + +# Git (Gitea) +GIT_TOKEN=your-gitea-token + +# Алерты +ALERTS_DATASOURCE_UID=af84zsvlp9blsa +``` + +--- + +## Флаги запуска + +| Флаг | Переменная окружения | По умолчанию | Описание | +|---|---|---|---| +| `-db-host` | — | `10.100.10.8` | PostgreSQL хост | +| `-db-port` | — | `5432` | PostgreSQL порт | +| `-db-user` | `DB_USER` | — | PostgreSQL пользователь | +| `-db-password` | `DB_PASSWORD` | — | PostgreSQL пароль | +| `-db-name` | — | `waf_info` | PostgreSQL база данных | +| `-grafana-url` | `GRAFANA_URL` | — | URL Grafana | +| `-grafana-api-key` | `GRAFANA_API_KEY` | — | Grafana API ключ | +| `-grafana-folder` | `GRAFANA_FOLDER` | `WAF - Auto Generated` | Папка для дашбордов | +| `-dashboard-title` | `DASHBOARD_TITLE` | `PT AF Nodes` | Название дашборда | +| `-alerts-folder` | `ALERTS_FOLDER` | `WAF - PTAF` | Папка для алертов | +| `-alerts-receiver` | `ALERTS_RECEIVER` | `Telegram PTAF Grafana` | Получатель уведомлений | +| `-alerts-group` | `ALERTS_GROUP` | `PTAF Grafana` | Группа алертов | +| `-alerts-datasource-uid` | `ALERTS_DATASOURCE_UID` | `af84zsvlp9blsa` | UID datasource OpenSearch | +| `-git-token` | `GIT_TOKEN` | — | Gitea токен | +| `-templates-repo` | — | `svc-git.cirex.ru/...` | URL Git-репозитория шаблонов | +| `-templates-branch` | — | `master` | Ветка шаблонов | +| `-templates-path` | — | `/etc/grafana_gen/templates` | Локальный путь шаблонов | +| `-skip-git-pull` | — | `false` | Не обновлять шаблоны из Git | +| `-state-file` | — | `/var/lib/grafana_gen/state.json` | Путь к state-файлу | +| `-dry-run` | — | `false` | Не отправлять в Grafana, только логировать | +| `-force` | — | `false` | Пересоздать всё даже без изменений | + +### Примеры запуска + +```bash +# Обычный запуск +./grafana_gen + +# Тестовый прогон без отправки в Grafana +./grafana_gen -dry-run + +# Принудительное пересоздание всего +./grafana_gen -force + +# Без обновления шаблонов из Git +./grafana_gen -skip-git-pull + +# Явное указание всех параметров +./grafana_gen \ + -db-host=10.100.10.8 \ + -db-user=reader \ + -db-password=secret \ + -grafana-url=https://grafana.example.com \ + -grafana-api-key=glsa-xxx +``` \ No newline at end of file diff --git a/README.md b/README.md index c97816d..563c44b 100644 --- a/README.md +++ b/README.md @@ -1,344 +1,71 @@ -# grafana_gen — Руководство пользователя +# grafana_gen -Утилита автоматически генерирует дашборды Grafana на основе данных из PostgreSQL. При каждом запуске она читает список клиентов и их ресурсов из БД, сравнивает с предыдущим состоянием и пересоздаёт единый дашборд только при наличии изменений. +Утилита автоматической генерации Grafana-дашбордов и правил алертинга для клиентов WAF/PTAF. ---- +## Что делает -## Как это работает +- Читает список клиентов и их SID из PostgreSQL (`sp_info` + `manual_info`) +- Генерирует единый дашборд с панелями на каждого клиента +- Создаёт правила алертинга: RPS, 4xx/5xx ошибки (пороги из БД), CPU, RAM, диск, контейнеры, Angie +- Обновляет шаблоны дашборда и алертов из Git-репозитория при каждом запуске +- Пропускает запуск если ничего не изменилось (отслеживает хэши) -``` -PostgreSQL (test_info) - ↓ - Список клиентов и ресурсов (SID, domain_name, aliases) - ↓ - Проверка изменений (state.json) - ↓ нет изменений → выход - Загрузка шаблона (dashboard_template.json из Git) - ↓ - Генерация JSON дашборда - ↓ - Grafana API → создание/обновление дашборда -``` - -На каждый запуск создаётся **один дашборд** со всеми клиентами. Каждый клиент отображается как свёрнутая строка (collapsed row), внутри которой — панели по каждому ресурсу. - ---- - -## Требования - -- Go 1.21+ -- PostgreSQL с базой `test_info` -- Grafana с API-доступом (Service Account Token или Legacy API Key) -- Git (для загрузки шаблонов) -- Доступ к Gitea-репозиторию с шаблоном - ---- - -## Установка и запуск - -### Сборка - -```bash -git clone <репозиторий утилиты> -cd grafana_gen -go build -o grafana_gen . -``` - -### Первый запуск +## Быстрый старт ```bash +# Минимальный запуск ./grafana_gen \ - -grafana-url https://grafana.example.com \ - -grafana-api-key glsa_xxxxxxxxxxxx \ - -db-user waf_reader \ - -db-password secret -``` + -db-user=reader \ + -db-password=secret \ + -grafana-url=https://grafana.example.com \ + -grafana-api-key=glsa-xxx -### Через .env файл (рекомендуется) - -Создайте файл `/etc/grafana_gen/grafana_gen.env`: - -```env -GRAFANA_URL=https://grafana.example.com -GRAFANA_API_KEY=glsa_xxxxxxxxxxxx -DB_USER=waf_reader -DB_PASSWORD=secret -GIT_TOKEN=your-gitea-token -``` - -Затем просто: - -```bash +# Или через .env файл (приоритет ниже переменных окружения) +cp grafana_gen.env.example /etc/grafana_gen/grafana_gen.env ./grafana_gen ``` ---- - ## Конфигурация -### Способы задать параметры (в порядке приоритета) +Параметры разрешаются в порядке приоритета: +**флаг → переменная окружения → .env файл → константа в config.go** -| Приоритет | Способ | Пример | -|-----------|--------|--------| -| 1 | Флаг командной строки | `-grafana-url https://...` | -| 2 | Переменная окружения | `export GRAFANA_URL=https://...` | -| 3 | `.env` файл | `GRAFANA_URL=https://...` | -| 4 | Константа в `config.go` | `DefaultGrafanaURL = "https://..."` | +Минимально необходимые параметры: -### Пути поиска .env файла +| Параметр | Флаг | Переменная окружения | +|---|---|---| +| PostgreSQL пользователь | `-db-user` | `DB_USER` | +| PostgreSQL пароль | `-db-password` | `DB_PASSWORD` | +| URL Grafana | `-grafana-url` | `GRAFANA_URL` | +| Grafana API ключ | `-grafana-api-key` | `GRAFANA_API_KEY` | -Утилита ищет `.env` файл в следующем порядке: +Пример `.env` файла: -1. Путь из переменной `GRAFANA_GEN_ENV_FILE` -2. `/etc/grafana_gen/grafana_gen.env` -3. `./grafana_gen.env` (рядом с бинарником) -4. `./.env` - ---- - -## Флаги командной строки - -### База данных - -| Флаг | Переменная окружения | По умолчанию | Описание | -|------|---------------------|--------------|----------| -| `-db-host` | — | `10.100.10.8` | Хост PostgreSQL | -| `-db-port` | — | `5432` | Порт PostgreSQL | -| `-db-user` | `DB_USER` | — | Пользователь БД (**обязательно**) | -| `-db-password` | `DB_PASSWORD` | — | Пароль БД (**обязательно**) | -| `-db-name` | — | `test_info` | Имя базы данных | -| `-use-manual` | — | `false` | Использовать таблицу `manual_info` вместо `sp_info` | - -### Grafana - -| Флаг | Переменная окружения | По умолчанию | Описание | -|------|---------------------|--------------|----------| -| `-grafana-url` | `GRAFANA_URL` | — | URL Grafana (**обязательно**) | -| `-grafana-api-key` | `GRAFANA_API_KEY` | — | API-ключ Grafana (**обязательно**) | -| `-grafana-folder` | `GRAFANA_FOLDER` | `WAF - Auto Generated` | Папка в Grafana для дашборда | -| `-dashboard-title` | `DASHBOARD_TITLE` | `PT AF Nodes` | Название дашборда | - -### Git / шаблоны - -| Флаг | Переменная окружения | По умолчанию | Описание | -|------|---------------------|--------------|----------| -| `-git-token` | `GIT_TOKEN` | — | Токен доступа к Gitea | -| `-templates-repo` | — | *(см. config.go)* | URL Git-репозитория с шаблонами | -| `-templates-branch` | — | `master` | Ветка репозитория | -| `-templates-path` | — | `/etc/grafana_gen/templates` | Локальный путь для шаблонов | -| `-skip-git-pull` | — | `false` | Не обновлять шаблоны из Git | - -### Управление запуском - -| Флаг | По умолчанию | Описание | -|------|--------------|----------| -| `-dry-run` | `false` | Сгенерировать JSON, но не отправлять в Grafana | -| `-force` | `false` | Принудительная регенерация даже без изменений | -| `-state-file` | `/var/lib/grafana_gen/state.json` | Путь к файлу состояния | - ---- - -## Приоритет параметров - -Пример: если одновременно задан флаг `-grafana-api-key`, переменная `GRAFANA_API_KEY` и значение в `.env` — используется **флаг командной строки** как наиболее приоритетный. - -``` -Флаг CLI > Переменная окружения > .env файл > константа в config.go +```env +DB_USER=grafana_reader +DB_PASSWORD=secret +GRAFANA_URL=https://grafana.example.com +GRAFANA_API_KEY=glsa-xxxxxxxxxxxxxxxxxxxx +GIT_TOKEN=your-gitea-token ``` -Это позволяет безопасно хранить секреты в `.env` и при необходимости переопределять их на лету без изменения файлов. - ---- - -## State-файл - -Утилита сохраняет состояние после каждого успешного запуска в JSON-файл (по умолчанию `/var/lib/grafana_gen/state.json`). - -При следующем запуске сравниваются: - -- Коммит шаблона в Git -- Версия шаблона (`version` в `dashboard_template.json`) -- SHA-256 хэш данных из БД (список клиентов, доменов, SID) -- Количество клиентов и доменов - -Если ничего не изменилось — дашборд не пересоздаётся, утилита завершается с кодом 0. - -``` -=== No Changes Detected === -No changes in templates or database since last run. -Skipping dashboard generation. - -Use -force flag to regenerate anyway. -``` - -Если изменения есть — выводится подробный отчёт: - -``` -=== Changes Detected === -Changes detected: - Template commit changed: abc12345 -> def67890 - Database content changed - - New SIDs: [SID_001, SID_002] -``` - ---- - -## Режим dry-run - -Позволяет проверить что будет сгенерировано без отправки в Grafana: +## Полезные флаги ```bash -./grafana_gen -dry-run +-dry-run # Сгенерировать и показать в логах, не отправлять в Grafana +-force # Пересоздать всё даже если изменений нет +-skip-git-pull # Не обновлять шаблоны из Git ``` -В лог выводится превью JSON дашборда и итоговая статистика: +## Шаблоны -``` -DRY RUN: Dashboard generated but not sent to Grafana -=== Summary === -Clients: 12 -Skipped: 0 -``` +Дашборд и алерты строятся по JSON-шаблонам из Git-репозитория: ---- +- `dashboard_template.json` — структура и панели дашборда +- `alert_rules_template.json` — правила алертинга -## Запуск через cron +Изменение любого шаблона автоматически триггерит пересоздание при следующем запуске. -Рекомендуемый вариант — запуск каждые 15 минут: +## Подробнее -```cron -*/15 * * * * /usr/local/bin/grafana_gen >> /var/log/grafana_gen.log 2>&1 -``` - -Утилита сама определяет нужно ли обновлять дашборд — частые запуски без изменений завершаются мгновенно. - -### Systemd timer (альтернатива) - -`/etc/systemd/system/grafana-gen.service`: -```ini -[Unit] -Description=Grafana Dashboard Generator -After=network.target postgresql.service - -[Service] -Type=oneshot -EnvironmentFile=/etc/grafana_gen/grafana_gen.env -ExecStart=/usr/local/bin/grafana_gen -StandardOutput=journal -StandardError=journal -``` - -`/etc/systemd/system/grafana-gen.timer`: -```ini -[Unit] -Description=Run grafana-gen every 15 minutes - -[Timer] -OnBootSec=2min -OnUnitActiveSec=15min - -[Install] -WantedBy=timers.target -``` - -```bash -systemctl enable --now grafana-gen.timer -``` - ---- - -## Типичные сценарии - -### Первое развёртывание - -```bash -# 1. Создать .env -cp grafana_gen.env.example /etc/grafana_gen/grafana_gen.env -vim /etc/grafana_gen/grafana_gen.env - -# 2. Проверить без отправки -./grafana_gen -dry-run - -# 3. Создать дашборд -./grafana_gen -``` - -### Принудительное обновление после изменения шаблона - -```bash -./grafana_gen -force -``` - -### Переключение на резервную БД - -```bash -./grafana_gen -db-host 10.10.10.5 -``` - -### Тестирование с другим шаблоном - -```bash -./grafana_gen \ - -templates-path /tmp/my-templates \ - -skip-git-pull \ - -dry-run -``` - -### Использование таблицы ручного ввода - -```bash -./grafana_gen -use-manual -``` - ---- - -## Структура БД - -Утилита читает данные из двух таблиц базы `test_info`. - -### Таблица `sp_info` (основная) - -| Колонка | Тип | Описание | -|---------|-----|----------| -| `sid` | text | Идентификатор ресурса | -| `domain_name` | text | Доменное имя | -| `aliases` | jsonb | JSON-массив дополнительных доменов | - -### Таблица `apps_settings` (справочник клиентов) - -| Колонка | Тип | Описание | -|---------|-----|----------| -| `l7resourceid` | text | SID ресурса (связь с `sp_info.sid`) | -| `client_title` | text | Название клиента | - -Если `client_title` не найден — клиент группируется под именем `Unknown`. - -Таблица `manual_info` имеет ту же структуру, что и `sp_info`, и используется при флаге `-use-manual` для ручного управления данными без изменения основной таблицы. - ---- - -## Файловая система - -``` -/etc/grafana_gen/ -├── grafana_gen.env # Конфигурация (секреты) -└── templates/ # Клонированный Git-репозиторий с шаблонами - └── dashboard_template.json - -/var/lib/grafana_gen/ -└── state.json # Состояние последнего запуска - -/usr/local/bin/ -└── grafana_gen # Бинарник утилиты -``` - -### Права доступа - -```bash -# Директории -install -d -m 755 /etc/grafana_gen -install -d -m 755 /var/lib/grafana_gen - -# .env файл — только для владельца процесса -chmod 600 /etc/grafana_gen/grafana_gen.env -``` \ No newline at end of file +См. [ARCHITECTURE.md](ARCHITECTURE.md). \ No newline at end of file