diff --git a/README.md b/README.md index 3f8b3f5..2ef5f0f 100644 --- a/README.md +++ b/README.md @@ -96,6 +96,12 @@ go build -o auspex | `cfgPrimaryDBHost` | Основная БД | `10.100.10.8` | | `cfgSecondaryDBHost` | Резервная БД | `10.100.13.5` | | `SERVICES_TO_CHECK ` | Службы для проверки | `ansible,docker` | +| `EMAIL_SMTP_HOST` | Адрес почтового сервера | `smtp.example.com` | +| `EMAIL_SMTP_PORT` | Порт почтового сервера | `587` | +| `EMAIL_SMTP_USER` | Юзер для отправки | `auspex@example.com` | +| `EMAIL_SMTP_PASSWORD` | Пароль | `secret` | +| `EMAIL_FROM` | Юзер для отправки | `auspex@example.com` | +| `EMAIL_TO` | Получатели | `admin1@example.com, admin2@example.com` | ## Зависимости diff --git a/certcheck.go b/certcheck.go index f3010f1..8be2a51 100644 --- a/certcheck.go +++ b/certcheck.go @@ -193,4 +193,9 @@ func ccCheckAndAlert(cert ccCertInfo, hostname string) { } else { log.Println("✅ Алерт успешно отправлен в Telegram") } + if err := emailSendHTML("Auspex: Срок действия сертификата", message); err != nil { + log.Printf("Ошибка отправки Email: %v", err) + } else { + log.Println("✅ Алерт успешно отправлен на Email") + } } diff --git a/config.go b/config.go index ccdaa08..91c791c 100644 --- a/config.go +++ b/config.go @@ -27,6 +27,16 @@ var ( cfgTelegramChatID = "" ) +// --- Email / SMTP --- +var ( + cfgEmailSMTPHost = "" + cfgEmailSMTPPort = 587 + cfgEmailSMTPUser = "" + cfgEmailSMTPPassword = "" + cfgEmailFrom = "" + cfgEmailTo = []string{} +) + // --- PostgreSQL Database --- var ( cfgDBUser = "" @@ -247,6 +257,36 @@ func applyConfig(env map[string]string) { } cfgServicesToCheck = services } + + // Email / SMTP + if v, ok := env["EMAIL_SMTP_HOST"]; ok && v != "" { + cfgEmailSMTPHost = v + } + if v, ok := env["EMAIL_SMTP_PORT"]; ok && v != "" { + if port, err := strconv.Atoi(v); err == nil { + cfgEmailSMTPPort = port + } + } + if v, ok := env["EMAIL_SMTP_USER"]; ok && v != "" { + cfgEmailSMTPUser = v + } + if v, ok := env["EMAIL_SMTP_PASSWORD"]; ok && v != "" { + cfgEmailSMTPPassword = v + } + if v, ok := env["EMAIL_FROM"]; ok && v != "" { + cfgEmailFrom = v + } + if v, ok := env["EMAIL_TO"]; ok && v != "" { + parts := strings.Split(v, ",") + recipients := make([]string, 0, len(parts)) + for _, s := range parts { + s = strings.TrimSpace(s) + if s != "" { + recipients = append(recipients, s) + } + } + cfgEmailTo = recipients + } } // ============================================================================= @@ -260,6 +300,12 @@ func isTelegramConfigured() bool { cfgTelegramChatID != "YOUR_CHAT_ID" } +func isEmailConfigured() bool { + return cfgEmailSMTPHost != "" && + cfgEmailFrom != "" && + len(cfgEmailTo) > 0 +} + // printConfigStatus выводит статус загруженной конфигурации func printConfigStatus() { fmt.Println("📋 Статус конфигурации:") @@ -278,6 +324,13 @@ func printConfigStatus() { fmt.Println(" ⚠️ База данных: не настроена") } + // Email + if isEmailConfigured() { + fmt.Printf(" ✅ Email: %s → %s (smtp: %s:%d)\n", cfgEmailFrom, strings.Join(cfgEmailTo, ", "), cfgEmailSMTPHost, cfgEmailSMTPPort) + } else { + fmt.Println(" ⚠️ Email: не настроен (EMAIL_SMTP_HOST, EMAIL_FROM, EMAIL_TO)") + } + // Services if len(cfgServicesToCheck) > 0 { fmt.Printf(" ✅ Службы для проверки: %s\n", strings.Join(cfgServicesToCheck, ", ")) diff --git a/dockercheck.go b/dockercheck.go index 5640e5b..99b4300 100644 --- a/dockercheck.go +++ b/dockercheck.go @@ -502,6 +502,11 @@ func dcSendAlert(hostname string, changes []dcStateChange) { } else { log.Println("✅ Алерт успешно отправлен в Telegram") } + if err := emailSendHTML("Auspex: Проблемы с контейнерами", message); err != nil { + log.Printf("❌ Ошибка отправки Email: %v", err) + } else { + log.Println("✅ Алерт успешно отправлен на Email") + } } // ── Вспомогательные функции ────────────────────────────────────────────────── diff --git a/email.go b/email.go new file mode 100644 index 0000000..6e85514 --- /dev/null +++ b/email.go @@ -0,0 +1,85 @@ +package main + +import ( + "fmt" + "log" + "net/smtp" + "strings" +) + +// emailSendHTML отправляет HTML-письмо через SMTP. +// Telegram-совместимые теги (, , ) корректно отображаются в HTML-письме. +// Если email не настроен или произошла ошибка — пишет в лог и не паникует. +func emailSendHTML(subject, message string) error { + // Проверяем минимально необходимые параметры + if !isEmailConfigured() { + log.Printf("[Email] Не настроен (нужны EMAIL_SMTP_HOST, EMAIL_FROM, EMAIL_TO) — пропускаем") + return fmt.Errorf("email не настроен") + } + + // Предупреждаем если авторизация неполная — попытку всё равно делаем, + // некоторые SMTP-серверы работают без auth (например внутренние relay) + if cfgEmailSMTPUser == "" && cfgEmailSMTPPassword == "" { + log.Printf("[Email] ⚠️ EMAIL_SMTP_USER и EMAIL_SMTP_PASSWORD не заданы — попытка без авторизации") + } else if cfgEmailSMTPUser == "" { + log.Printf("[Email] ⚠️ EMAIL_SMTP_USER не задан — авторизация невозможна, попытка без неё") + } else if cfgEmailSMTPPassword == "" { + log.Printf("[Email] ⚠️ EMAIL_SMTP_PASSWORD не задан — авторизация невозможна, попытка без неё") + } + + body := emailBuildHTML(subject, message) + addr := fmt.Sprintf("%s:%d", cfgEmailSMTPHost, cfgEmailSMTPPort) + + var auth smtp.Auth + if cfgEmailSMTPUser != "" && cfgEmailSMTPPassword != "" { + auth = smtp.PlainAuth("", cfgEmailSMTPUser, cfgEmailSMTPPassword, cfgEmailSMTPHost) + } + + header := strings.Join([]string{ + "From: " + cfgEmailFrom, + "To: " + strings.Join(cfgEmailTo, ", "), + "Subject: " + subject, + "MIME-Version: 1.0", + "Content-Type: text/html; charset=UTF-8", + "", + }, "\r\n") + + msg := []byte(header + "\r\n" + body) + + log.Printf("[Email] Отправка: %s → %s (smtp: %s)", cfgEmailFrom, strings.Join(cfgEmailTo, ", "), addr) + + if err := smtp.SendMail(addr, auth, cfgEmailFrom, cfgEmailTo, msg); err != nil { + return fmt.Errorf("ошибка отправки письма (smtp: %s): %w", addr, err) + } + + return nil +} + +// emailBuildHTML оборачивает тело сообщения в HTML-шаблон письма. +// Telegram-теги уже являются валидным HTML, поэтому конвертация не нужна. +func emailBuildHTML(subject, body string) string { + // Заменяем переносы строк на
для корректного отображения в письме + htmlBody := strings.ReplaceAll(body, "\n", "
\n") + + return fmt.Sprintf(` + + + + %s + + + +
+ %s + +
+ +`, subject, htmlBody, appVersion) +} diff --git a/logcheck.go b/logcheck.go index 1dba378..286aebe 100644 --- a/logcheck.go +++ b/logcheck.go @@ -477,4 +477,9 @@ func lcSendAlerts(hostname string, staleFiles []lcLogFileStatus) { } else { log.Println("✅ Алерт успешно отправлен в Telegram") } + if err := emailSendHTML("Auspex: Устаревшие логи", message); err != nil { + log.Printf("❌ Ошибка отправки Email: %v", err) + } else { + log.Println("✅ Алерт успешно отправлен на Email") + } } diff --git a/main.go b/main.go index e3d7b41..ed0b38f 100644 --- a/main.go +++ b/main.go @@ -7,7 +7,7 @@ import ( "strings" ) -const appVersion = "1.2.0" +const appVersion = "1.3.0" // Цвета терминала (общие для всех модулей) const ( diff --git a/servicecheck.go b/servicecheck.go index cdd4db3..509b5e1 100644 --- a/servicecheck.go +++ b/servicecheck.go @@ -313,6 +313,11 @@ func scSendAlert(hostname string, changes []scStateChange) { } else { log.Println("✅ Алерт успешно отправлен в Telegram") } + if err := emailSendHTML("Auspex: Проблемы со службами", message); err != nil { + log.Printf("❌ Ошибка отправки Email: %v", err) + } else { + log.Println("✅ Алерт успешно отправлен на Email") + } } // ── Вспомогательные функции ──────────────────────────────────────────────────