docs: fix 12 documentation inaccuracies in README.md and AGENTS.md
README.md fixes: - Architecture diagram now shows pulse-lets-go-agent layer - Features list: added agent, auto-trunk, WS gateway, user-01 protection - route.lua example: replaced fake curl.fetch() with real api:execute() - systemd unit: Wants=nats.service removed (NATS on separate host) - Gateway mapping: removed grep 'pulse-' filter (misses existing gateways) - Production deployment: added agent deployment step (between NATS and route.lua) - Makefile commands: added build-agent, build-siptest, siptest-*, emulate-* AGENTS.md fixes: - Structure: added missing cmd/emulator, cmd/siptest, cmd/pulse-lets-go-agent, esl/, ami/, contrib/ - NATS model: added sip_gateway field - config.json: added nats_user, nats_password, tls, rate_limit, log, esl blocks - Internal packages: added esl/ and ami/ - route.lua name: route_call → pulse_route (matches production)
This commit is contained in:
parent
dac3b4ce40
commit
af6a439452
38
AGENTS.md
38
AGENTS.md
@ -28,7 +28,8 @@
|
||||
"max_calls": 250,
|
||||
"idle_cpu": 35,
|
||||
"load_avg": 0.85,
|
||||
"call_failure_rate": 0.5
|
||||
"call_failure_rate": 0.5,
|
||||
"sip_gateway": "sip:pbx-01.lan:5060"
|
||||
}
|
||||
```
|
||||
|
||||
@ -174,7 +175,7 @@ Response (нет нод вообще):
|
||||
FS использует Lua скрипт в dialplan → `mod_curl` дёргает `/api/route` → получает JSON → парсит `sip_gateway` → bridge.
|
||||
|
||||
```
|
||||
<extension name="route_call">
|
||||
<extension name="pulse_route">
|
||||
<condition field="destination_number" expression="^.*$">
|
||||
<action application="lua" data="route.lua"/>
|
||||
</condition>
|
||||
@ -188,6 +189,8 @@ route.lua: GET `/api/route?...` → если `fallback: true` идёт на fall
|
||||
```json
|
||||
{
|
||||
"nats_url": "nats://localhost:4222",
|
||||
"nats_user": "",
|
||||
"nats_password": "",
|
||||
"listen_addr": ":8080",
|
||||
"jwt_secret": "auto-generated-on-first-run",
|
||||
"monitoring_api_key": "auto-generated-on-first-run",
|
||||
@ -201,6 +204,28 @@ route.lua: GET `/api/route?...` → если `fallback: true` идёт на fall
|
||||
"idle_score": 0.20,
|
||||
"fail_score": 0.10
|
||||
}
|
||||
},
|
||||
"tls": {
|
||||
"enabled": false,
|
||||
"cert_file": "",
|
||||
"key_file": ""
|
||||
},
|
||||
"rate_limit": {
|
||||
"enabled": true,
|
||||
"route_per_sec": 5000,
|
||||
"api_per_sec": 100
|
||||
},
|
||||
"log": {
|
||||
"level": "info",
|
||||
"format": "text"
|
||||
},
|
||||
"esl": {
|
||||
"host": "",
|
||||
"port": 8021,
|
||||
"password": "",
|
||||
"password_env": "",
|
||||
"sofia_profile": "external",
|
||||
"gateway_prefix": "pulse"
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -210,13 +235,19 @@ route.lua: GET `/api/route?...` → если `fallback: true` идёт на fall
|
||||
```
|
||||
pulse-lets-go/
|
||||
├── cmd/pulse-lets-go/main.go
|
||||
├── cmd/pulse-lets-go-agent/
|
||||
├── cmd/emulator/
|
||||
├── cmd/siptest/
|
||||
├── internal/
|
||||
│ ├── api/ — handlers: auth, nodes, trunks, users, monitoring, ws
|
||||
│ ├── engine/ — scorer + router (sync.RWMutex, best_node кэш)
|
||||
│ ├── nats/ — subscriber, in-memory node store
|
||||
│ ├── esl/ — FreeSWITCH ESL client (raw TCP)
|
||||
│ ├── ami/ — Asterisk AMI client (raw TCP)
|
||||
│ ├── config/ — manager: чтение/атомарная запись JSON
|
||||
│ ├── models/ — все общие типы
|
||||
│ └── log/ — ASCII-metrics логгер + ротация
|
||||
├── contrib/ — route.lua, agent конфиги
|
||||
├── data/ — JSON конфиги (gitignored)
|
||||
├── web/ — SvelteKit + shadcn-svelte + Tailwind
|
||||
│ ├── src/routes/
|
||||
@ -224,7 +255,8 @@ pulse-lets-go/
|
||||
│ │ ├── +page.svelte # дашборд
|
||||
│ │ ├── login/+page.svelte
|
||||
│ │ ├── trunks/+page.svelte
|
||||
│ │ └── nodes/+page.svelte
|
||||
│ │ ├── nodes/+page.svelte
|
||||
│ │ └── users/+page.svelte
|
||||
│ ├── src/lib/
|
||||
│ │ ├── components/
|
||||
│ │ └── api.ts
|
||||
|
||||
164
README.md
164
README.md
@ -13,13 +13,17 @@
|
||||
|
||||
## Features
|
||||
|
||||
- **pulse-lets-go-agent** — агент сбора метрик для FreeSWITCH (ESL) и Asterisk (AMI). Работает на каждой PBX-ноде.
|
||||
- **Scoring engine** — lethal checks (disabled, stale, overload, CPU, failure rate) + weighted score (call, load, idle, fail) с конфигурируемыми весами.
|
||||
- **NATS шина** — асинхронный сбор метрик через `pulse.metrics.<node_id>`.
|
||||
- **REST API** — JWT (access + refresh), роли admin/viewer, rate limiting, CORS.
|
||||
- **Auto-создание транков** — при получении первой метрики от новой ноды с `sip_gateway` создаётся balance-транк автоматически.
|
||||
- **Gateway health WS** — статус FS-gateway (registered/unregistered) транслируется на фронт через WebSocket.
|
||||
- **REST API** — JWT (access + refresh), роли admin/viewer, rate limiting, CORS, health endpoints.
|
||||
- **WebSocket** — real-time трансляция метрик на SvelteKit дашборд.
|
||||
- **FreeSWITCH ESL** — управление Sofia-шлюзами через raw TCP ESL.
|
||||
- **Monitoring** — Prometheus и Zabbix эндпоинты с отдельным API-key.
|
||||
- **SvelteKit UI** — дашборд, таблицы нод, транков, пользователей.
|
||||
- **Защита пользователей** — первичный администратор (user-01) не может быть удалён или понижен до viewer.
|
||||
- **Graceful shutdown** — корректное завершение с таймаутом 10 с.
|
||||
- **systemd ready** — unit с изоляцией (NoNewPrivileges, ProtectSystem, PrivateTmp).
|
||||
|
||||
@ -28,46 +32,54 @@
|
||||
## Архитектура
|
||||
|
||||
```
|
||||
┌────────────────┐ pulse.metrics.<id> ┌──────────────────┐
|
||||
│ PBX-01 │────── every 5s ────────────▶ │ │
|
||||
│ (FS node) │ │ NATS Server │
|
||||
├────────────────┤ │ nats://:4222 │
|
||||
│ PBX-02 │────── every 5s ────────────▶ │ │
|
||||
│ (FS node) │ └────────┬─────────┘
|
||||
├────────────────┤ │
|
||||
│ PBX-03 │────── every 5s ────────────▶ │
|
||||
│ (FS node) │ │
|
||||
└────────────────┘ ▼
|
||||
┌──────────────────────┐
|
||||
│ pulse-lets-go │
|
||||
│ │
|
||||
│ ┌────────────────┐ │
|
||||
┌───────▶│ NATS Sub │ │
|
||||
│ │ (pulse.metrics)│ │
|
||||
│ └────────┬───────┘ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ Engine │ │
|
||||
│ │ (scoring) │──│──▶ /api/route
|
||||
│ └────────┬───────┘ │ ↓
|
||||
│ ▼ │ FreeSWITCH bridge
|
||||
│ ┌────────────────┐ │ sip:pbx-03:5060
|
||||
│ │ REST API │──│──▶ /api/nodes
|
||||
│ │ (JWT auth) │ │ /api/trunks
|
||||
│ └────────┬───────┘ │ /api/users
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ WebSocket │──│──▶ SvelteKit UI
|
||||
│ └────────────────┘ │ (ws://:8080/ws/metrics)
|
||||
│ ┌────────────────┐ │
|
||||
│ │ ESL Client │──│──▶ FreeSWITCH ESL
|
||||
│ │ (gateways) │ │ :8021
|
||||
│ └────────────────┘ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ Monitoring │──│──▶ /api/monitoring/prometheus
|
||||
│ │ (API-key) │ │ /api/monitoring/zabbix
|
||||
│ └────────────────┘ │
|
||||
└──────────────────────┘
|
||||
┌─────────────────────┐ ESL/AMI ┌──────────────────────┐
|
||||
│ PBX-01 │──────────▶│ pulse-lets-go-agent │
|
||||
│ (FS or Asterisk) │ collect │ (on each PBX node) │
|
||||
├─────────────────────┤ ├──────────────────────┤
|
||||
│ PBX-02 │──────────▶│ pulse-lets-go-agent │
|
||||
│ (FS or Asterisk) │ ├──────────────────────┤
|
||||
├─────────────────────┤ ├──────────────────────┤
|
||||
│ PBX-N │──────────▶│ pulse-lets-go-agent │
|
||||
│ (FS or Asterisk) │ └─────────┬────────────┘
|
||||
└─────────────────────┘ │ pulse.metrics.<id>
|
||||
│ every 5s
|
||||
▼
|
||||
┌──────────────┐
|
||||
│ NATS Server │
|
||||
│ nats://:4222 │
|
||||
└──────┬───────┘
|
||||
│
|
||||
▼
|
||||
┌──────────────────────┐
|
||||
│ pulse-lets-go │
|
||||
│ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ NATS Sub │ │
|
||||
│ │ (pulse.metrics)│ │
|
||||
│ └────────┬───────┘ │
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ Engine │ │
|
||||
│ │ (scoring) │──│──▶ /api/route
|
||||
│ └────────┬───────┘ │ ↓
|
||||
│ ▼ │ FreeSWITCH bridge
|
||||
│ ┌────────────────┐ │ sip:pbx-03:5060
|
||||
│ │ REST API │──│──▶ /api/nodes
|
||||
│ │ (JWT auth) │ │ /api/trunks
|
||||
│ └────────┬───────┘ │ /api/users
|
||||
│ ▼ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ WebSocket │──│──▶ SvelteKit UI
|
||||
│ └────────────────┘ │ (ws://:8080/ws/metrics)
|
||||
│ ┌────────────────┐ │
|
||||
│ │ ESL Client │──│──▶ FreeSWITCH ESL
|
||||
│ │ (gateways) │ │ :8021
|
||||
│ └────────────────┘ │
|
||||
│ ┌────────────────┐ │
|
||||
│ │ Monitoring │──│──▶ /api/monitoring/prometheus
|
||||
│ │ (API-key) │ │ /api/monitoring/zabbix
|
||||
│ └────────────────┘ │
|
||||
└──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
@ -336,20 +348,24 @@ curl -H "X-API-Key: $(jq -r '.monitoring_api_key' data/config.json)" \
|
||||
FreeSWITCH использует Lua-скрипт в dialplan. Скрипт дёргает `/api/route` через `mod_curl` и бриджится на полученный gateway.
|
||||
|
||||
```lua
|
||||
-- contrib/route.lua (упрощённо)
|
||||
local url = string.format("%s/api/route?caller_id=%s&dest=%s&ingress_trunk=%s",
|
||||
session:getVariable("balancer_url"),
|
||||
session:getVariable("caller_id_number"),
|
||||
session:getVariable("destination_number"),
|
||||
session:getVariable("ingress_trunk"))
|
||||
-- contrib/route.lua (сокращённо)
|
||||
api = freeswitch.API()
|
||||
caller_id = session:getVariable("caller_id_number") or ""
|
||||
dest = session:getVariable("destination_number") or ""
|
||||
url = "http://localhost:8080/api/route?caller_id=" .. caller_id .. "&dest=" .. dest
|
||||
|
||||
local response = curl.fetch({url = url, timeout = tonumber(route_timeout)})
|
||||
local ok, result = cjson.decode(response.body)
|
||||
raw = api:execute("curl", url)
|
||||
body = raw:match("\r?\n\r?\n(.+)") or raw
|
||||
|
||||
if result.fallback then
|
||||
session:bridge(result.sip_gateway)
|
||||
local ok, route = pcall(cjson.decode, body)
|
||||
if not ok then session:hangup("NORMAL_TEMPORARY_FAILURE") return end
|
||||
|
||||
if route.fallback then
|
||||
session:bridge(route.sip_gateway) -- оператор
|
||||
elseif route.sip_gateway then
|
||||
session:bridge(route.sip_gateway) -- лучшая нода
|
||||
else
|
||||
session:bridge(result.sip_gateway)
|
||||
session:hangup("NORMAL_TEMPORARY_FAILURE")
|
||||
end
|
||||
```
|
||||
|
||||
@ -415,7 +431,7 @@ make install-systemd # устанавливает systemd unit
|
||||
# deploy/pulse-lets-go.service
|
||||
[Unit]
|
||||
Description=Pulse Lets Go — Telephone Load Balancer
|
||||
Wants=nats.service
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
User=pulse
|
||||
@ -498,13 +514,28 @@ sudo mkdir -p /opt/nats
|
||||
# Запуск: nats-server -p 4222 -D &
|
||||
```
|
||||
|
||||
**Шаг 3 — route.lua в FS scripts**
|
||||
**Шаг 3 — Деплой agent на PBX-ноды** (повторить для каждой UC/Asterisk ноды)
|
||||
```bash
|
||||
# Скопировать бинарник и конфиг на каждую PBX:
|
||||
scp bin/pulse-lets-go-agent admin@PBX_IP:/usr/local/bin/
|
||||
scp contrib/agent-uc.json admin@PBX_IP:/etc/pulse-lets-go-agent/agent.json
|
||||
|
||||
# Запустить:
|
||||
ssh admin@PBX_IP "systemctl start pulse-lets-go-agent"
|
||||
|
||||
# Проверить лог:
|
||||
# journalctl -u pulse-lets-go-agent -f
|
||||
# → [ami] подключён к 127.0.0.1:6154
|
||||
# → [agent] опубликована метрика: calls=0/150 load=0.28 cpu=99% status=ok
|
||||
```
|
||||
|
||||
**Шаг 4 — route.lua в FS scripts**
|
||||
```bash
|
||||
sudo mkdir -p /etc/freeswitch/scripts
|
||||
sudo cp /tmp/route.lua /etc/freeswitch/scripts/
|
||||
```
|
||||
|
||||
**Шаг 4 — Dialplan** (добавить `pulse_route` extension)
|
||||
**Шаг 5 — Dialplan** (добавить `pulse_route` extension)
|
||||
```xml
|
||||
<!-- В /etc/freeswitch/dialplan/default.xml перед остальными extension -->
|
||||
<extension name="pulse_route">
|
||||
@ -518,17 +549,18 @@ sudo cp /tmp/route.lua /etc/freeswitch/scripts/
|
||||
sudo fs_cli -x "reloadxml"
|
||||
```
|
||||
|
||||
**Шаг 5 — Маппинг существующих FS gateway в pulse-lets-go транки**
|
||||
**Шаг 6 — Создание balance-транков для каждой PBX-ноды**
|
||||
```bash
|
||||
# Получить список gateway из FS:
|
||||
echo "api sofia profile external gwlist" | nc 127.0.0.1 8021 | grep "pulse-"
|
||||
# → список имён gateway
|
||||
# Получить список gateway из FS (или список нод из конфига):
|
||||
echo "api sofia profile external gwlist" | nc 127.0.0.1 8021
|
||||
|
||||
# Создать balance-транки для каждого gateway через API:
|
||||
# POST /api/trunks {"name":"uc-69 баланс","type":"balance","node_id":"uc-69","gateway":"sip:10.x.x.x:5060"}
|
||||
# Создать balance-транк для каждой UC-ноды:
|
||||
curl -X POST .../api/trunks
|
||||
-d '{"name":"uc06","type":"balance","node_id":"uc06","gateway":"sip:uc-pbx.lan:5060","enabled":true}'
|
||||
```
|
||||
Транки также создаются автоматически при получении первой метрики от агента (если в метрике передан `sip_gateway`).
|
||||
|
||||
**Шаг 6 — Запуск pulse-lets-go**
|
||||
**Шаг 7 — Запуск pulse-lets-go**
|
||||
```bash
|
||||
sudo mkdir -p /etc/pulse-lets-go /var/log/pulse-lets-go
|
||||
# config.json создастся автоматически при первом запуске
|
||||
@ -537,7 +569,7 @@ sudo mkdir -p /etc/pulse-lets-go /var/log/pulse-lets-go
|
||||
PULSE_DATA_DIR=/etc/pulse-lets-go ./pulse-lets-go
|
||||
```
|
||||
|
||||
**Шаг 7 — Проверка**
|
||||
**Шаг 8 — Проверка**
|
||||
```bash
|
||||
curl http://127.0.0.1:8080/api/health | jq '.connections'
|
||||
# → {"nats":"connected","esl":"connected"}
|
||||
@ -751,8 +783,10 @@ curl http://localhost:8080/api/nodes | jq '.[] | {node_id, score, active_calls}'
|
||||
| `make test-race` | Тесты с race detector |
|
||||
| `make lint` | `go vet ./...` |
|
||||
| `make fmt` | `go fmt ./...` |
|
||||
| `make emulate-normal` | Эмуляция метрик (3 здоровые ноды) |
|
||||
| `make emulate-chaos` | Эмуляция случайных сбоев |
|
||||
| `make build-agent` | Сборка pulse-lets-go-agent |
|
||||
| `make build-siptest` | Сборка SIP-тестера |
|
||||
| `make emulate-*` | Эмуляция метрик (normal, overload, chaos, stale, ...) |
|
||||
| `make siptest-*` | SIP-тестирование (uas, uac, full) |
|
||||
|
||||
### Стек
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user