@Eugene Sukhodolskiy Eugene Sukhodolskiy authored 5 hours ago
docs docs: спека health-эндпоинта перенесена в gnexus-handbook 6 hours ago
monitor feat: deploy.sh — one-command agent rollout over SSH (+ README guide) 24 days ago
panel feat: страницы сервисов — персональный график + журнал недоступности 5 hours ago
.dockerignore feat: stage 3 — Vue web UI (dashboard, server page, charts) + docker build 24 days ago
.gitignore feat: stage 3 — Vue web UI (dashboard, server page, charts) + docker build 24 days ago
README.md feat: страницы сервисов — персональный график + журнал недоступности 5 hours ago
docker-compose.yml feat: close the panel behind gnexus-auth (OAuth PKCE) 9 hours ago
README.md

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 + 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 — отдельной БД не надо.

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

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

cp panel/backend/.env.example .env

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

# порт, на котором панель слушает снаружи (по умолчанию 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):

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. Запуск:

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 то же самое:

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

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

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

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

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 и запустит. Повторный запуск — обновление агента.

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

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

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

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

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

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

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-доступом к машинам. Промт для агента:

Разверни 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 — на сервере с панелью:

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

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

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. Полный бэкап:

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. Панель — референс-реализация парсера §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):

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:

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):

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 или оставь доступ только из доверенной сети, иначе любой сможет читать метрики.

Статус

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