hermes: deploy Telegram readiness diagnostics

This commit is contained in:
jenkins 2026-08-13 13:05:17 -03:00
parent 569a366cbb
commit 987bd7587c
3 changed files with 57 additions and 11 deletions

View File

@ -20,7 +20,7 @@ spec:
app: hermes-chat-router
annotations:
ai.bstein.dev/role: privacy-preserving-chat-tenant-router
ai.bstein.dev/config-rev: "20260813-telegram-readiness-v1"
ai.bstein.dev/config-rev: "20260813-telegram-readiness-v2"
vault.hashicorp.com/agent-inject: "true"
vault.hashicorp.com/agent-pre-populate-only: "true"
vault.hashicorp.com/agent-init-first: "true"
@ -62,7 +62,7 @@ spec:
values: [rpi5]
containers:
- name: router
image: registry.bstein.dev/bstein/hermes-chat-router@sha256:d639e78934d36384eaebc631a95c7e151492c59af2d483c994f14ac585590eeb
image: registry.bstein.dev/bstein/hermes-chat-router@sha256:e3794fb8b9ee76d699893ba303d7080f54698c0a06064dc32293e99e988cabff
imagePullPolicy: IfNotPresent
ports:
- {name: http, containerPort: 8080, protocol: TCP}

View File

@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
@ -100,14 +101,18 @@ func (bot *telegramBot) status() (string, bool, string) {
return bot.botUsername, bot.ready, bot.lastError
}
func (bot *telegramBot) setStatus(username string, ready bool, lastError string) {
func (bot *telegramBot) setStatus(username string, ready bool, lastError string) bool {
bot.mu.Lock()
defer bot.mu.Unlock()
previousUsername := bot.botUsername
previousReady := bot.ready
previousError := bot.lastError
if username != "" {
bot.botUsername = strings.TrimPrefix(strings.TrimSpace(username), "@")
}
bot.ready = ready
bot.lastError = strings.TrimSpace(lastError)
return previousUsername != bot.botUsername || previousReady != bot.ready || previousError != bot.lastError
}
func (bot *telegramBot) slotLock(slot int) *sync.Mutex {
@ -142,9 +147,18 @@ func (bot *telegramBot) call(ctx context.Context, method string, values url.Valu
var envelope struct {
OK bool `json:"ok"`
Result json.RawMessage `json:"result"`
Description string `json:"description"`
ErrorCode int `json:"error_code"`
}
if response.StatusCode != http.StatusOK || json.Unmarshal(body, &envelope) != nil || !envelope.OK {
return fmt.Errorf("Telegram API returned status %d", response.StatusCode)
if err := json.Unmarshal(body, &envelope); err != nil {
return fmt.Errorf("Telegram API returned undecodable status %d", response.StatusCode)
}
if response.StatusCode != http.StatusOK || !envelope.OK {
description := strings.TrimSpace(envelope.Description)
if description == "" {
description = http.StatusText(response.StatusCode)
}
return fmt.Errorf("Telegram API error %d: %s", envelope.ErrorCode, description)
}
if result != nil && len(envelope.Result) > 0 {
if err := json.Unmarshal(envelope.Result, result); err != nil {
@ -164,10 +178,21 @@ func (bot *telegramBot) run() {
err := bot.call(ctx, "getMe", url.Values{}, &me)
cancel()
if err == nil {
bot.setStatus(me.Username, true, "")
ctx, cancel = context.WithTimeout(context.Background(), 15*time.Second)
err = bot.call(ctx, "deleteWebhook", url.Values{
"drop_pending_updates": {"false"},
}, nil)
cancel()
if err == nil {
if bot.setStatus(me.Username, true, "") {
log.Printf("Telegram bot ready as @%s", strings.TrimPrefix(me.Username, "@"))
}
break
}
bot.setStatus("", false, "Telegram rejected the configured bot token or is temporarily unavailable.")
}
if bot.setStatus("", false, "Telegram rejected the configured bot token or is temporarily unavailable.") {
log.Printf("Telegram bot readiness check failed: %v", err)
}
time.Sleep(10 * time.Second)
}
for {
@ -182,7 +207,9 @@ func (bot *telegramBot) run() {
err := bot.call(ctx, "getUpdates", values, &updates)
cancel()
if err != nil {
bot.setStatus("", false, "Hermes lost contact with the configured Telegram bot.")
if bot.setStatus("", false, "Hermes lost contact with the configured Telegram bot.") {
log.Printf("Telegram update polling failed: %v", err)
}
time.Sleep(3 * time.Second)
break
}

View File

@ -8,6 +8,7 @@ import (
"path/filepath"
"strings"
"testing"
"time"
)
func TestReadTelegramConfig(t *testing.T) {
@ -72,3 +73,21 @@ func TestSplitTelegramTextStaysUnderTelegramLimit(t *testing.T) {
}
}
}
func TestTelegramCallIncludesAPIDescription(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
writer.Header().Set("Content-Type", "application/json")
writer.WriteHeader(http.StatusConflict)
_, _ = writer.Write([]byte(`{"ok":false,"error_code":409,"description":"Conflict: terminated by other getUpdates request"}`))
}))
defer server.Close()
bot := &telegramBot{
apiBase: server.URL,
client: &http.Client{Timeout: time.Second},
}
err := bot.call(t.Context(), "getUpdates", nil, nil)
if err == nil || !strings.Contains(err.Error(), "terminated by other getUpdates request") {
t.Fatalf("expected Telegram API description, got %v", err)
}
}