# GHard Monitor

**Gnexus Hardware Monitor** — сбор мониторинга всех серверов в одном месте.

```
┌───────────────┐   POST /api/v1/ingest   ┌────────────────────┐
│ hard-monitor  │ ─────────────────────► │  hard-panel        │
│ (агент,       │   метрики + ключ       │  FastAPI + SQLite  │
│  stateless)   │                        │  + Vue UI          │
└───────────────┘                        └────────────────────┘
```

- **panel/** — панель: приём метрик, SQLite, ивенты, алерты, веб-интерфейс
  (Vue 3 + [gnexus-ui-kit](https://git.gnexus.space/root/gnexus-ui-kit) + chart.js)
- **monitor/** — агент на серверах: psutil-сбор фактов, POST на панель.
  Агент stateless, вся логика (пороги, ивенты, скорость сети по дельте
  счётчиков) — на панели.

## Стек

| Часть | Технологии |
|---|---|
| panel backend | Python 3.12, FastAPI, aiosqlite (SQLite, WAL) |
| panel frontend | Vue 3, gnexus-ui-kit, chart.js |
| monitor | Python 3.8+ (psutil, requests), systemd |
| Деплой | Docker / docker-compose |

## Разворачивание panel

Нужен сервер с docker и docker compose. SQLite живёт в volume — отдельной
БД не надо.

```bash
git clone https://git.gnexus.space/git/root/hard-panel.git
cd hard-panel
```

**1. Конфиг** — корень репо, рядом с docker-compose.yml:

```bash
cp panel/backend/.env.example .env
```

Отредактируй `.env`:

```ini
# порт, на котором панель слушает снаружи (по умолчанию 8000)
GHARD_PANEL_PORT=8000
```

`GHARD_ADMIN_TOKEN` — опционален: веб-интерфейс работает без логина.
Если задать токен, API панели (создание серверов, метрики) закроется
Bearer-токеном — удобно, когда панель торчит в интернет, а UI тебе не нужен.
Единственная настоящая авторизация в системе — ключи агентов (`ghm_...`).

## Авторизация через gnexus-auth

По умолчанию панель открыта (личный инструмент). Если панель читает
несколько человек, её можно закрыть единым входом gnexus-auth —
веб-интерфейс потребует кнопку **Sign in** (OAuth PKCE) вместо доступа без логина.
Скрипты и MCP при этом продолжают работать с Bearer-токеном `GHARD_ADMIN_TOKEN`,
агенты — со своими ключами `X-Server-Key` (см. матрицу ниже).

**1. Зарегистрируй OAuth-клиент** в админке gnexus-auth (`/admin/clients`):

- redirect URI — `http://<адрес-панели>/auth/callback` (то, что в браузере)
- scopes — `openid email profile roles permissions`
- webhook URL — `http://<адрес-панели>/webhooks/gnexus-auth` (авто-логаут
  при `global_logout` в gnexus-auth, обновление имени/аватарки в сессии)

**2. Пропиши в `.env`** (см. `panel/backend/.env.example`):

```ini
GHARD_AUTH_BASE_URL=https://gnexus-auth.example.com
GHARD_AUTH_CLIENT_ID=gnx_hard_panel
GHARD_AUTH_CLIENT_SECRET=...
GHARD_AUTH_REDIRECT_URI=...
GHARD_AUTH_WEBHOOK_SECRET=...
# опционально: пусто = пускаем любого залогиненного, иначе список через запятую
# GHARD_AUTH_ALLOWLIST=user@example.com
```

и `docker compose up -d --build`. Всё — при открытии панели будет кнопка Sign in;
сессия живёт в cookie `ghard_session` (httponly) `GHARD_SESSION_TTL_SECONDS` (7 суток).

### Матрица доступа

| Кто | Как авторизуется | Зависит от |
|---|---|---|
| Браузер | cookie-сессия gnexus-auth (`/auth/login` → PKCE → callback) | `GHARD_AUTH_CLIENT_ID` задан |
| Скрипты, MCP | `Authorization: Bearer <GHARD_ADMIN_TOKEN>` | `GHARD_ADMIN_TOKEN` задан |
| Агент (`hard-monitor`) | заголовок `X-Server-Key: ghm_…` (только ingest своего сервера) | ключ сервера в БД панели |

Если все три выключены — панель полностью открыта (личный режим).
Любая включённая строка закрывает `/api/v1/*` только для себя: токен и
cookie взаимозаменяемы, агентский ключ проверяется только на `/ingest`.

**2. Запуск:**

```bash
docker compose up -d --build
```

Проверка: `curl http://localhost:8000/api/v1/health` → `{"status":"ok"}`.
OpenAPI-документация: `http://localhost:8000/docs`.

**3. Reverse proxy (рекомендуется)** — агенты будут слать метрики из
интернета, поэтому панель стоит закрыть TLS-ом. Пример Caddy:

```
panel.example.com {
    reverse_proxy 127.0.0.1:8000
}
```

Caddy сам выпустит сертификат. Для nginx: `proxy_pass http://127.0.0.1:8000;`
и обычный certbot. Заголовок `X-Forwarded-For` панель учитывает — в
карточке сервера будет виден реальный IP агента.

## Добавление сервера и установка агента

Проще всего — кнопка «Добавить сервер» в веб-интерфейсе. Через API то же самое:

```bash
curl -X POST https://panel.example.com/api/v1/servers \
    -H "Content-Type: application/json" \
    -d '{"name": "web-01"}'
```

Ответ — **plaintext-ключ, показывается один раз**, сохрани сразу:

```json
{"id": 1, "name": "web-01", "key": "ghm_KyfA9jheG9OSZxFlfFmdx8yFpO3tZAcO"}
```

Дальше на целевом сервере (python3 + pip):

```bash
curl -fsSL https://git.gnexus.space/root/hard-panel/raw/branch/master/monitor/install.sh | \
    sudo bash -s -- https://panel.example.com ghm_KyfA9jheG9OSZ... 30
```

Скрипт поставит всё в `/opt/hard-monitor` (venv + psutil + requests),
запишет `.env` с правами 600, установит systemd-юнит `hard-monitor` и
запустит. Повторный запуск — обновление агента.

Логи и управление:

```bash
journalctl -u hard-monitor -f      # смотреть логи
systemctl restart hard-monitor     # рестарт
```

Проверка на панели — сервер должен получить `status: "online"`:

```bash
curl -s https://panel.example.com/api/v1/servers
```

## Массовое развёртывание агентов (в том числе ИИ-агентом)

`monitor/deploy.sh` делает весь цикл сам: создаёт сервер на панели →
заходит по SSH → ставит агента → ждёт первый пакет. Одна команда на
любое число хостов:

```bash
cd monitor
./deploy.sh --panel https://panel.example.com user@web-01 user@db-01 user@cache-01
```

Опции:

| Опция | Что делает |
| --- | --- |
| `--panel URL` | адрес панели (обязательно) |
| `--token TOKEN` | GHARD_ADMIN_TOKEN — нужен, только если токен на панели задан |
| `--interval N` | интервал агента в секундах (по умолчанию 30) |
| `--recreate` | удалить существующую запись сервера на панели и создать заново (новый ключ); без него ключ переиспользуется из `/opt/hard-monitor/.env` на хосте |
| `--git-install` | ставить curl-ом из репозитория; по умолчанию файлы агента заливаются scp с локальной машины |

Требования к хостам: SSH-доступ по ключу, `python3`, `sudo` без пароля.
Повторный запуск на уже развёрнутом хосте — обновление агента
(запись на панели и ключ сохраняются).

### Развёртывание ИИ-агентом (Claude Code и т.п.)

Скрипт специально сделан неинтерактивным и идемпотентным, чтобы его мог
запустить ИИ-агент с SSH-доступом к машинам. Промт для агента:

```text
Разверни hard-monitor на моих серверах. Панель: https://panel.example.com
1. Склонируй https://git.gnexus.space/git/root/hard-panel.git (нужна папка monitor/)
2. Запусти: cd monitor && ./deploy.sh --panel https://panel.example.com user@host1 user@host2 ...
3. Если какой-то хост упал с ошибкой — разберись (ssh, journalctl -u hard-monitor),
   почини и перезапусти deploy.sh только для этого хоста
4. Проверь, что все серверы online: curl -s https://panel.example.com/api/v1/servers
   (у каждого status: "online")
```

Агенту не нужен доступ к UI панели: серверы создаются через API, ключи
никогда не показываются человеку и не сохраняются — install.sh сразу
кладёт их в `/opt/hard-monitor/.env` (права 600).

## Обновление

**Panel** — на сервере с панелью:

```bash
cd hard-panel
git pull
docker compose up -d --build
```

**Агент** — на каждом сервере (нужны свежие `hard_monitor.py` /
`hard-monitor.service` в текущем каталоге — склонируй репо):

```bash
git clone https://git.gnexus.space/git/root/hard-panel.git && cd hard-panel/monitor
sudo ./install.sh   # с теми же PANEL_URL/SERVER_KEY — перезапишет файлы и рестартанёт
```

Или вручную: скопируй `hard_monitor.py` в `/opt/hard-monitor/` и
`systemctl restart hard-monitor`. Конфиг `.env` не трогается.

## Бэкапы

Вся панель — один файл SQLite в volume `panel-data`. Полный бэкап:

```bash
docker compose exec panel python -c \
    "import sqlite3; sqlite3.connect('/data/hard-panel.db').backup(sqlite3.connect('/data/backup.db'))"
docker compose cp panel:/data/backup.db ./hard-panel-backup.db
```

(или просто остановить и скопировать volume). Ключи серверов в БД —
хэши, восстановить plaintext из бэкапа нельзя: потерянный ключ —
пересоздай сервер.

## Конфигурация panel (env)

| Переменная | По умолчанию | Описание |
|---|---|---|
| `GHARD_ADMIN_TOKEN` | *(пусто)* | опциональный Bearer-токен API; веб-интерфейс работает без логина |
| `GHARD_DATABASE_PATH` | `/data/hard-panel.db` | путь к SQLite |
| `GHARD_PANEL_PORT` | `8000` | host-порт (docker-compose) |
| `GHARD_OFFLINE_MULTIPLIER` | `3` | offline = нет пакетов дольше N × interval |
| `GHARD_SHARE_INTERVAL` | `60` | период опроса сетевых хранилищ, сек |

## API (кратко)

- `POST /api/v1/servers` — создать сервер, получить ключ агента (один раз)
- `POST /api/v1/ingest` — пакет метрик агента, заголовок `X-Server-Key`
- `GET /api/v1/servers` — сводка для дашборда
- `GET /api/v1/servers/{id}` — карточка + последняя точка
- `GET /api/v1/servers/{id}/metrics?since=…&until=…` — история для графиков
- `PATCH /api/v1/servers/{id}` — имя / заметка
- `DELETE /api/v1/servers/{id}` — удалить сервер (с каскадом метрик)
- `GET/POST/PATCH/DELETE /api/v1/shares[/{id}]` — сетевые хранилища (CRUD)
- `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}/incidents?limit=50` — журнал моментов
  недоступности (периоды down+degraded из истории проб; у идущего периода
  `end=null`)
- `GET/PATCH /api/v1/me` — профиль + язык (GET), override языка (PATCH; только
  cookie-сессия — у Bearer-админа нет личности)

## Сервисы (health-чеки)

Раздел «Сервисы» следит за здоровьем внешних gnexus-сервисов: добавляете
health-URL (например `http://gnexus-creds.local/api/v1/health`) — панель сама
опрашивает его каждые `GHARD_HEALTH_INTERVAL` сек (все сервисы параллельно,
таймаут `GHARD_HEALTH_TIMEOUT` сек) и хранит историю 7 дней (state, HTTP-код,
latency_ms).

Формат health-эндпоинтов gnexus ещё не договорён — панель работает по
прогрессивному минимуму: 2xx = ok; тело-JSON со строкой `status` учитывается
(ok-набор → ok, degraded-набор → деградация, всё прочее → вниз); не-2xx/таймаут
= вниз. Полный контракт и рекомендации для сервисов — конвенция экосистемы в
handbook: [10-platform/health.md](https://git.gnexus.space/root/gnexus-handbook/raw/master/10-platform/health.md).
Панель — референс-реализация парсера §5 «Самосвидетельство»: из расширенного тела
читаются identity (`service/version/build/environment/uptime`), `mode`
(обслуживание/readonly/draining — нейтральный бейдж) и `gauges` (числа,
чипами в карточке). Сырое тело не хранится; известные поля берутся по белому
списку.

Общий график «все сервисы» в списке не показывается (масштабы latency не
сравнимы). У каждого сервиса своя страница (`/services/{id}`): персональный
график времени отклика с периодами 1h/6h/24h/7d (down-точки — разрыв линии),
журнал моментов недоступности — периоды бездействия `down` + `degraded`
по истории проб со «сворачиванием» подряд идущих не-up точек (worst-состояние
в периоде, код/причина из первой точки; идущий период — метка
«продолжается»). Там же — редактирование (имя/URL; смена URL даёт
немедленный повторный замер) и удаление сервиса.

## Язык интерфейса

Три языка: en, uk, ru. По умолчанию панель следует языку аккаунта gnexus-auth
(`profile.locale`, меняется в самом gnexus-auth). В разделе «Настройки» первого
блока — «Язык интерфейса»: выбор перекрывает язык аккаунта только для панели;
`Авто` возвращает язык аккаунта. Выбор переживает logout и обновление профиля
(webhook не трогает override). Если gnexus-auth не настроен — панель открыта,
язык хранится в localStorage браузера и не синхронизируется между устройствами.

## Сетевые хранилища (раздел Storage)

Панель умеет следить за контентом на сетевых накопителях (NAS): хранилище
примонтируется к машине, где работает panel, а панель сама опрашивает его
через `os.statvfs` — отдельно раздел Storage в веб-интерфейсе, точки
заполнения каждые `GHARD_SHARE_INTERVAL` секунд, история за 7 дней.

Примонтировать SMB/CIFS-шару (Debian/Ubuntu):

```bash
sudo apt install cifs-utils
# /etc/nas-creds: username=..., password=..., domain=...
sudo mount -t cifs //192.168.1.50/media /mnt/media-nas -o credentials=/etc/nas-creds,uid=1000,iocharset=utf8
```

NFS:

```bash
sudo apt install nfs-common
sudo mount -t nfs nas.local:/export/media /mnt/media-nas
```

Постоянно — через `/etc/fstab`:

```
//192.168.1.50/media  /mnt/media-nas  cifs  credentials=/etc/nas-creds,uid=1000,iocharset=utf8,_netdev,nofail  0  0
nas.local:/export/media /mnt/media-nas nfs  defaults,_netdev,nofail  0  0
```

Затем в панели: **Storage → Add share**, имя и путь монтирования
(`/mnt/media-nas`). Статусы: **online** (замер удался), **offline**
(примонтированный путь недоступен — отвалилась сеть/NAS), **waiting**
(данных ещё нет или пробер молчит). Отвал NAS показывается как offline,
данные при этом панель не трогает. `_netdev,nofail` в fstab нужен, чтобы
загрузка панели не зависла, если NAS в момент старта выключен.

## MCP для ИИ-агентов

Панель сама является MCP-сервером (streamable HTTP, по умолчанию `/mcp`)
— любой ИИ-агент подключается по URL панели и читает состояние серверов.

Инструменты:

| Tool | Что отдаёт |
| --- | --- |
| `panel_overview` | все серверы одной сводкой: статус, CPU/RAM/диск, сеть, последний пакет; плюс сетевые хранилища с заполнением |
| `server_details` | полное состояние сервера: метрики, диски, процессы, docker, заметка |
| `server_history` | история метрик за N часов, прореженная до N точек |

Подключение (Claude Code):

```bash
claude mcp add --transport http ghard https://panel.example.com/mcp
```

Для любого другого MCP-клиента — тот же URL, транспорт streamable HTTP.
После подключения агент может отвечать на вопросы «как мои серверы?»,
«что тормозит на web-01?», «растёт ли диск на db-01 за сутки?» без
доступа к веб-интерфейсу.

**Заметка про токен**: `/mcp` сейчас открыт так же, как REST API при
пустом `GHARD_ADMIN_TOKEN`. Если панель торчит в интернет и ты задаёшь
токен — закрой `/mcp` на reverse proxy или оставь доступ только из
доверенной сети, иначе любой сможет читать метрики.

## Статус

- [x] Этап 1 — скелет: FastAPI, SQLite-схема, ingest, servers API, docker
- [x] Этап 2 — hard-monitor агент + install.sh + systemd
- [ ] Этап 3 — веб-интерфейс (Vue + ui-kit + chart.js)
- [ ] Этап 4 — ивенты + алерты (Telegram, webhook)
- [ ] Этап 5 — downsample истории (7 дней), финальный деплой