Files
nettopo-go/README.md
T
2026-04-10 13:22:59 +10:00

273 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# nettopo-go
Минимальный старт проекта сетового инвентаризатора и топологии на Go без Docker.
## Что уже есть
- `GET /health` - проверка состояния API.
- `POST /api/scans` - создание задания скана.
- `GET /api/scans` - список сканов.
- `GET /api/scans/{id}` - просмотр созданного задания.
- `GET /api/scans/diff?from=&to=` - сравнение двух сканов (hosts/links).
- `GET /api/scans/{id}/hosts` - результаты проверки хостов по скану.
- `GET /api/scans/{id}/ports` - результаты проверки TCP-портов по скану.
- `GET /api/scans/{id}/snmp` - результаты SNMP v2c (`sysName`, `sysDescr`, `sysObjectID`).
- `GET /api/scans/{id}/lldp` - LLDP соседи (если устройство отдает LLDP-MIB по SNMP).
- `GET /api/scans/{id}/links` - базовые связи из LLDP с попыткой сопоставления по `remote_sys_name`.
- `GET /api/scans/{id}/topology` - граф `nodes/edges` для визуализации.
- `GET /` — UI для просмотра топологии (страница встроена в бинарник через `embed`, каталог `internal/webui/`; **не зависит** от `WorkingDirectory` и папки `web/` рядом с процессом).
- Валидация CIDR и exclude IP.
- Два backend-хранилища: in-memory и PostgreSQL.
- После создания scan запускается фоновый discovery с обновлением статуса и прогресса.
- Отдельный чек-лист ручной приемки: `MANUAL_TEST_CHECKLIST.md`.
- План продолжения на завтра: `NEXT_STEPS.md`.
## Требования
- Go 1.22+ (достаточно пакета `golang-go` из Ubuntu 24.04). Зависимость `pgx` зафиксирована на **v5.7.4**, потому что **v5.7.5+** объявляет `go 1.23` и не соберётся на Go 1.22 из apt. При желании можно поднять toolchain до **Go 1.23+** и снова обновить `pgx`.
Проверка версии:
```bash
go version
```
## Зависимости Go (`go.mod`, `go.sum`)
В репозитории закоммичен **`go.sum`**: в нём зафиксированы контрольные суммы модулей из `go.mod`. Это нужно, чтобы на новом клоне (например на сервере после `git pull`) команда `go build` не завершалась ошибкой вида `missing go.sum entry for module ...`. Исходники зависимостей в git не копируются — при сборке модули по-прежнему загружаются через прокси модулей (`proxy.golang.org` или ваш `GOPROXY`), если не используете `vendor`.
После изменения зависимостей выполните **`go mod tidy`** и закоммитьте обновлённые `go.mod` и `go.sum`. Проверка целостности: **`go mod verify`**.
Если на машине **нет доступа в интернет** во время сборки, можно закоммитить каталог **`vendor/`** (команда `go mod vendor`) и собирать так: **`go build -mod=vendor -o nettopo-server ./cmd/server`**.
## Запуск локально
1. Перейдите в проект:
```bash
cd nettopo-go
```
2. Скопируйте переменные окружения:
```bash
cp .env.example .env
```
3. Запустите API:
```bash
HTTP_ADDR=:8080 go run ./cmd/server
```
UI открывается по адресу:
```bash
http://localhost:8080/
```
Что умеет UI сейчас:
- загрузить topology по `scan_id`;
- автоматически подставить последний scan (`Последний scan`);
- сравнить два scan прямо на странице (`from`/`to`) через `/api/scans/diff`.
- выбрать scan из выпадающего списка последних 20;
- подсветить на графе новые (`[+]`, зеленый) и пропавшие (`[-]`, красный) хосты по diff.
- использовать легенду цветов и фильтр узлов: все / только новые / только пропавшие.
- экспортировать topology и diff в JSON-файлы кнопками из UI.
- создать scan по CIDR из UI и дождаться завершения кнопкой "Ждать завершения".
По умолчанию используется `STORE_BACKEND=memory`.
SNMP-проверка по умолчанию включена (`SNMP_ENABLED=true`, community `public`).
## Логика одного scan (что делает программа)
Один запуск scan — это **одна фоновая задача** (`runner`), без отдельных фаз «только ping» / «только LLDP» в API.
1. По полю `cidrs` строится список IP (с учётом `exclude_ips`).
2. Параллельно (ограничение `max_parallel_hosts`) для каждого IP:
- **ping** (ICMP через системную утилиту `ping`);
- если хост «вверх» и включён port-scan — проверка **TCP-портов** из `options.ports`;
- если хост «вверх» и `SNMP_ENABLED=true`**SNMP v2c** (sysName, sysDescr, sysObjectID) и сразу **обход LLDP-MIB** (соседи, локальный/удалённый порт и т.д.). Это не отдельная кнопка: LLDP выполняется в том же проходе, что и базовый SNMP.
3. Статус задачи: `queued``running` (поле `progress` 0…100, `stats.hosts_total` / `stats.hosts_up`) → `done` или `failed`.
4. Результаты читаются через API: `/hosts`, `/ports`, `/snmp`, `/lldp`, агрегированный граф — `/topology`.
В веб-UI после **«Создать scan и ждать»** опрашивается статус, показывается расшифровка в строке состояния, по завершении обновляется таблица **найденных устройств** (ping + SNMP) и строится граф по кнопке **«Загрузить»** (или автоматически после ожидания).
### LLDP пустой на коммутаторе (например Ubiquiti EdgeSwitch)
Опрос LLDP идёт по стандартному **LLDP-MIB** (`1.0.8802.1.1.2.1.4.1.1.*`) после успешного SNMP **Get** системной группы. Раньше при **GET-BULK** часть прошивок отвечала слишком долго или с ошибкой — обход обрывался, в БД не попадало ни строки («No LLDP data» в UI). Сейчас для фазы LLDP увеличен таймаут (не меньше **3 c** на запрос), снижен размер «пачки» **MaxRepetitions**, добавлен **повтор при ошибке через SNMP Walk (GetNext)**. В логах сервера ищите строки `lldp snmp BulkWalkAll` и `fallback Walk`. Проверьте с хоста nettopo: community (**`SNMP_COMMUNITY` / `public`**), доступ UDP **161** с этого сервера и что на свиче включён **SNMP v2c** (не только LLDP в L2, но и ответ агента SNMP).
## PostgreSQL без Docker
### Вариант A: macOS (Homebrew)
```bash
brew install postgresql@16
brew services start postgresql@16
createdb nettopo
createuser nettopo
```
```bash
psql -d postgres -c "alter user nettopo with password 'nettopo';"
psql -d postgres -c "grant all privileges on database nettopo to nettopo;"
```
### Вариант B: Ubuntu/Debian
```bash
sudo apt update
sudo apt install -y postgresql postgresql-contrib
sudo -u postgres psql -c "create user nettopo with password 'nettopo';"
sudo -u postgres psql -c "create database nettopo owner nettopo;"
```
### Вариант C: Windows
- Установить PostgreSQL через официальный installer.
- Создать БД `nettopo` и пользователя `nettopo` (через pgAdmin или `psql`).
### Запуск API с PostgreSQL
```bash
export STORE_BACKEND=postgres
export DB_DSN='postgres://nettopo:nettopo@localhost:5432/nettopo?sslmode=disable'
export RUN_MIGRATIONS=true
go run ./cmd/server
```
При первом запуске автоматически применится миграция `scan_jobs`.
## Быстрая проверка API
Проверка health:
```bash
curl -s http://localhost:8080/health
```
Создать scan:
```bash
curl -s -X POST http://localhost:8080/api/scans \
-H "Content-Type: application/json" \
-d '{
"name":"Office scan",
"cidrs":["192.168.1.0/24"],
"exclude_ips":["192.168.1.1"],
"options":{
"ping_timeout_ms":700,
"ping_retries":1,
"max_parallel_hosts":128,
"port_scan_enabled":true,
"ports":[22,80,443,161]
}
}'
```
Получить scan по id:
```bash
curl -s http://localhost:8080/api/scans/<scan_id>
```
Получить список scan jobs:
```bash
curl -s http://localhost:8080/api/scans?limit=20
```
Сравнить два scan:
```bash
curl -s "http://localhost:8080/api/scans/diff?from=<scan_id_1>&to=<scan_id_2>"
```
В ответе у scan есть:
- `status`: `queued`, `running`, `done`, `failed`
- `progress`: 0..100
- `stats.hosts_total`, `stats.hosts_up`
Получить результаты по хостам:
```bash
curl -s http://localhost:8080/api/scans/<scan_id>/hosts?limit=200
```
Получить результаты по портам:
```bash
curl -s http://localhost:8080/api/scans/<scan_id>/ports?limit=500
```
Получить SNMP результаты:
```bash
curl -s http://localhost:8080/api/scans/<scan_id>/snmp?limit=500
```
Получить LLDP результаты:
```bash
curl -s http://localhost:8080/api/scans/<scan_id>/lldp?limit=1000
```
Получить связи:
```bash
curl -s http://localhost:8080/api/scans/<scan_id>/links?limit=1000
```
Получить topology (nodes/edges):
```bash
curl -s http://localhost:8080/api/scans/<scan_id>/topology?limit=2000
```
## Запуск как сервис на Linux (systemd)
1. Собрать бинарник:
```bash
go build -o nettopo-server ./cmd/server
```
2. Скопировать unit-файл:
```bash
sudo cp deploy/nettopo-go.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nettopo-go
```
3. Проверить статус:
```bash
systemctl status nettopo-go
```
### Логи (systemd)
Сервер пишет в **стандартный лог** пакета `log` (stderr): старт, выбранное хранилище, фатальные ошибки БД. Просмотр:
```bash
journalctl -u nettopo-go -f
journalctl -u nettopo-go -n 100 --no-pager
```
Отдельного файла логов приложение **не создаёт**; при необходимости перенаправьте вывод unit-а в файл через `StandardOutput=` / `StandardError=` в override.
### Если в браузере `404 page not found` на `/`
Раньше UI отдавался из каталога `web/` относительно **текущей рабочей директории** процесса; при `WorkingDirectory=/opt/nettopo-go` без этой папки корень сайта давал 404. Сейчас UI **вшит в бинарник** — после обновления до актуального коммита пересоберите сервер (`go build ...`) и перезапустите сервис. Проверка API: `curl -s http://127.0.0.1:8080/health` должен вернуть JSON `{"status":"ok",...}`.
## Размещение за IIS на Windows
- Запускать `nettopo-server.exe` как Windows Service.
- На IIS настроить Reverse Proxy к `http://localhost:8080`.
- Требуются URL Rewrite + ARR.
Шаблон `web.config` лежит в `deploy/web.config.example`.