feat(instance): метрики и healthcheck инстанса для дашборда
Раздел «Инстанс» получает живые показатели сервера (AGENT.md 7.19). - `internal/sysinfo`: загрузка процессора по cgroup v2 (`cpu.stat`/`cpu.max`) с откатом на `/proc/stat`, память по лимиту контейнера либо `/proc/meminfo`, место на разделе данных и размер базы вместе с журналом WAL; - `GET /api/v1/instance/metrics` (только администратор инстанса): метрики, состояние (`ok|warn|failed`), проверки app/database/disk/storage/voice/ gateway, версия и время работы; недоступные источники не ломают ответ, а попадают в проверку `metrics`; - тяжёлые проверки (БД, SFU, запись) кэшируются на 5 секунд: ручку можно опрашивать раз в секунду; - `voice.IssueRoomList` и `AdminClient.Ping` проверяют доступность RoomService и валидность ключей LiveKit (та же связка, что ломала модерацию); - у метрик свой лимит частоты, они исключены из общего лимита API (`RateLimiter.MiddlewareExcept`), иначе открытый раздел съедал бы половину бюджета запросов; - тесты: `internal/sysinfo` (cgroup, /proc, память, диск, размеры файлов) и `internal/server/api_metrics_test.go` (доступ, состав метрик, кэш, 429).
This commit is contained in:
+23
-14
@@ -87,6 +87,11 @@ func (i *TokenIssuer) Issue(room, identity, displayName string, grants Grants) (
|
||||
claims.NotBefore = now.Add(-10 * time.Second).Unix()
|
||||
claims.ExpiresAt = now.Add(i.ttl).Unix()
|
||||
|
||||
return i.sign(claims)
|
||||
}
|
||||
|
||||
// sign подписывает произвольные claims алгоритмом HS256.
|
||||
func (i *TokenIssuer) sign(claims any) (string, error) {
|
||||
header, err := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
|
||||
if err != nil {
|
||||
return "", err
|
||||
@@ -142,20 +147,7 @@ func (i *TokenIssuer) IssueAdmin(room string) (string, error) {
|
||||
"roomAdmin": true,
|
||||
},
|
||||
}
|
||||
header, err := json.Marshal(map[string]string{"alg": "HS256", "typ": "JWT"})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
payload, err := json.Marshal(claims)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
signingInput := encodeSegment(header) + "." + encodeSegment(payload)
|
||||
mac := hmac.New(sha256.New, []byte(i.apiSecret))
|
||||
if _, err := mac.Write([]byte(signingInput)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return signingInput + "." + base64.RawURLEncoding.EncodeToString(mac.Sum(nil)), nil
|
||||
return i.sign(claims)
|
||||
}
|
||||
|
||||
// VerifyWebhook проверяет подпись вебхука LiveKit: это JWT, подписанный тем же
|
||||
@@ -272,6 +264,23 @@ func ResolveLiveKitURL(raw string) (string, error) {
|
||||
return strings.TrimSuffix(parsed.String(), "/"), nil
|
||||
}
|
||||
|
||||
// IssueRoomList подписывает служебный токен для запросов, которым не нужна
|
||||
// конкретная комната (проверка доступности RoomService в healthcheck).
|
||||
func (i *TokenIssuer) IssueRoomList() (string, error) {
|
||||
if !i.Enabled() {
|
||||
return "", ErrDisabled
|
||||
}
|
||||
return i.sign(map[string]any{
|
||||
"iss": i.apiKey,
|
||||
"sub": i.apiKey,
|
||||
"nbf": i.now().UTC().Add(-10 * time.Second).Unix(),
|
||||
"exp": i.now().UTC().Add(5 * time.Minute).Unix(),
|
||||
"video": map[string]any{
|
||||
"roomList": true,
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// VoiceURLForClient отдаёт адрес LiveKit так, как его должен использовать
|
||||
// клиент: пусто, если голос не настроен.
|
||||
func (i *TokenIssuer) VoiceURLForClient(publicURL string) string {
|
||||
|
||||
Reference in New Issue
Block a user