18 KiB
Auspex — Architecture
Обзор
Auspex — монолитное CLI-приложение на Go. Все модули живут в одном пакете main, конфигурация глобальная, каналы доставки алертов shared. Такой подход выбран осознанно — утилита запускается по cron, живёт секунды, сложная изоляция не нужна.
┌─────────────────────────────────────────────────────┐
│ auspex │
│ │
│ main.go ──► runByFlag() / showMenu() │
│ │ │
│ ┌──────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ certcheck portcheck logcheck ... │
│ │ │ │ │
│ └──────────┴──────────────┘ │
│ │ │
│ Alert Pipeline │
│ ┌──────────┼──────────────┐ │
│ ▼ ▼ ▼ │
│ telegram email mattermost │
└─────────────────────────────────────────────────────┘
Файлы и их роли
main.go — точка входа
Отвечает за два режима запуска:
CLI-флаги (runByFlag): прямой вызов нужного модуля по флагу (-c, -p, -l, -sv, -d, -m, -ss).
Интерактивное меню (showMenu): ASCII-баннер, numbered menu, bufio.Reader для ввода. Поддерживает как цифры (1-7), так и текстовые алиасы (-c, c).
auspex [флаг] [аргументы]
│
├── -ss → runPortsView()
├── -c → runCertCheck()
├── -p → runPortCheck()
├── -m → runMatchCheck(clientFilter)
├── -l → runLogCheck()
├── -sv → runServiceCheck()
├── -d → runDockerCheck()
├── -v → версия
└── -h → usage
config.go — конфигурация
Все настройки — глобальные переменные вида cfgXxx. Загрузка из /etc/auspex/auspex.env при старте через loadConfig() → applyConfig().
Формат .env:
KEY=VALUE # кавычки обрезаются автоматически
# комментарии игнорируются
Если файл не найден — используются значения по умолчанию, вывод предупреждения в stdout. Программа продолжает работу.
Группы переменных:
- Telegram (токен, chat ID, прокси)
- Mattermost (URL, токен бота, channel ID)
- Email/SMTP (хост, порт, логин, пароль, from, to)
- PostgreSQL (user, password, dbname, port, primary host, secondary host)
- Пути (SSL dir, nginx glob, log dir, state files)
- Пороги (cert days, log stale minutes)
- Параметры проверки (timeout, retries, workers)
- Список служб для проверки
Вспомогательные функции:
isTelegramConfigured()— проверяет наличие токена и chat IDisEmailConfigured()— проверяет host, from, toisMattermostConfigured()— проверяет URL, токен, channel IDprintConfigStatus()— выводит статус всех каналов при запуске
telegram.go — доставка в Telegram
Функции отправки:
telegramSendHTML(message)— HTML-форматирование (теги<b>,<code>,<i>)telegramSendPlainText(message)— plain text без форматирования (используется в portcheck)
Цепочка прокси (telegramHTTPClients):
Возвращает срез []*http.Client в порядке приоритета:
1. HTTP прокси (если задан TELEGRAM_HTTP_PROXY)
2. SOCKS5 (если задан TELEGRAM_SOCKS5_PROXY)
3. Прямое (всегда, как последний fallback)
telegramDoPost и telegramDoPostForm перебирают клиентов по очереди. При ошибке одного — переходят к следующему с логом предупреждения. Успешный вариант логируется.
SOCKS5: реализован через golang.org/x/net/proxy. Логин/пароль задаются отдельными переменными (не в URL) — это исключает проблемы со спецсимволами вроде ! и $ в паролях.
HTTP прокси: реализован через стандартный http.Transport{Proxy: http.ProxyURL(...)}.
email.go — доставка по Email
emailSendHTML(subject, message) — отправка HTML-письма через SMTP.
Особенности реализации:
- Telegram-теги (
<b>,<code>,<i>) являются валидным HTML и отображаются в письме без конвертации \nзаменяются на<br>для корректного отображения переносов строк- Письмо оборачивается в HTML-шаблон с CSS стилями (
emailBuildHTML) - Поддерживается
AUTH LOGIN(тип авторизации который использует Microsoft Exchange и многие корпоративные серверы) через кастомныйemailLoginAuth, реализующий интерфейсsmtp.Auth - Стандартный
smtp.PlainAuthнамеренно не используется — он несовместим с рядом серверов - Порт 587 + STARTTLS работает автоматически через
smtp.SendMail EMAIL_TOподдерживает несколько адресов через запятую
Диагностика при неполной конфигурации:
- Не задан HOST/FROM/TO → лог
[Email] Не настроен — пропускаем - Не задан USER или PASSWORD → предупреждение, попытка без авторизации (для relay-серверов)
- Ошибка SMTP → лог с кодом ответа и телом ошибки
mattermost.go — доставка в Mattermost
mattermostSend(message) — отправка через Bot API.
Endpoint: POST /api/v4/posts с заголовком Authorization: Bearer <token>.
Успешный ответ: 201 Created (не 200, как в большинстве API).
mmHTMLToMarkdown(html) — конвертер форматирования:
| HTML (Telegram) | Markdown (Mattermost) |
|---|---|
<b>текст</b> |
**текст** |
<i>текст</i> |
_текст_ |
<code>текст</code> |
`текст` |
<pre>текст</pre> |
```текст``` |
━━━━━ |
--- |
< > & |
< > & |
| Прочие теги | удаляются |
certcheck.go — проверка SSL сертификатов
Точка входа: runCertCheck()
Алгоритм:
ccFindCerts(cfgSSLDir)— рекурсивный обход директории, фильтрация по расширениям (.crt,.pem,.cer,.cert,.der,.p7b,.p7c,.p7s,.csr)- Параллельный парсинг через пул горутин (
cfgMaxWorkers) ccParseCert(path)— определение формата (PEM/DER), парсинг черезcrypto/x509, вычислениеdaysLeftccCheckAndAlert(cert)— сравнение с порогами и отправка алерта
Уровни алертов:
| Условие | Emoji | Уровень |
|---|---|---|
daysLeft < 0 |
💀💀💀 | Сертификат истёк |
daysLeft <= CERT_CRITICAL_DAYS |
🚨🚨🚨 | Критично |
daysLeft <= CERT_WARNING_DAYS |
⚠️⚠️ | Предупреждение |
daysLeft <= CERT_INFO_DAYS |
ℹ️ | Информация |
| Иначе | — | Алерт не нужен |
portcheck.go — проверка доступности портов
Точка входа: runPortCheck()
Алгоритм:
- Подключение к PostgreSQL (primary → secondary failover)
- Загрузка карты
check_portsиз таблицыapps_settings— какие конфиги проверять pcParseNginxConfig(file)— парсингupstream { server ip:port; }блоков regex'ом- Дедупликация endpoint'ов по всем файлам
- Параллельная TCP-проверка (
net.DialTimeout) с повторами (cfgMaxRetries) pcCompareStates— сравнение с предыдущим state-файлом- Отправка алерта только при изменениях
State-файл (/tmp/port_checker_state.json):
{
"timestamp": "2026-04-14 13:10:10",
"status": {
"91.206.126.83:443": {"endpoint": "...", "available": true, "config_file": "Client_12345"}
}
}
Имена конфигов строятся из имени файла без расширения: Client_12345.conf → Client_12345.
logcheck.go — проверка свежести логов
Точка входа: runLogCheck()
Алгоритм (4 шага через PostgreSQL):
lcGetInstancesByHostname— инстансы для текущего хоста (instances_new)lcGetClientsByInstances— клиенты для этих инстансов (client_info)lcGetAppsByClients— приложения сcheck_write_logs = true(apps_settings)lcCheckLogFile— поиск файла по паттерну{LOG_DIR}/*/*/{l7resourceid}*_access.log, проверкаModTime
Файл считается устаревшим если time.Since(ModTime) > cfgLogStaleMinutes.
Уровни в алерте по времени с последней записи:
| Минут | Emoji |
|---|---|
| 61–120 | ⚠️ |
| 121–240 | 🔥 |
| > 240 | 💀 |
matchcheck.go — проверка совпадений Angie ↔ Docker
Точка входа: runMatchCheck(clientFilter)
Проверяет соответствие между портами в конфигах Angie и запущенными Docker контейнерами. Поддерживает фильтрацию по имени клиента.
Алгоритм:
- Парсинг конфигов Angie из
cfgAngieConfDir— извлечение портов на которых слушает Angie docker ps— список запущенных контейнеров с их портами- Сопоставление: для каждого клиента проверяется есть ли соответствующий контейнер и совпадают ли порты
- Алерт при расхождениях
servicecheck.go — проверка служб systemd
Точка входа: runServiceCheck()
Алгоритм:
- Параллельная проверка всех служб из
cfgServicesToCheckчерез горутины scCheckService(name)—systemctl is-active <name>+systemctl show <name> --property=SubState,Description- Загрузка предыдущего state-файла
scCompareStates— сравнение текущих статусов с предыдущими- Алерт только при изменениях
State-файл (/tmp/auspex_service_state.json):
{
"timestamp": "2026-04-14 13:00:00",
"services": {
"angie": "active",
"docker": "failed"
}
}
Типы изменений в алерте:
| Изменение | Emoji |
|---|---|
active → любой другой |
❌ |
любой → active |
✅ (восстановление) |
failed |
💀 |
inactive |
🔴 |
activating/deactivating |
🟡 |
dockercheck.go — проверка Docker контейнеров
Точка входа: runDockerCheck()
Алгоритм:
dcCheckDockerAvailable()—docker infoдля проверки доступности демонаdcGetContainers()—docker ps -a --format '{{.ID}}|{{.Names}}|{{.Status}}|{{.Image}}'dcGetHealth(id)—docker inspect --format '{{if .State.Health}}...{{end}}'для каждого контейнера отдельно (.Healthнедоступен вdocker psв ряде сборок)- Нормализация статуса:
"Up 2 hours"→"running","Exited (1) 3 min ago"→"exited" dcCompareStates— сравнение с предыдущим state-файлом- Алерт только при изменениях
Типы событий:
| Kind | Условие | Emoji |
|---|---|---|
appeared |
Новый контейнер (не при первом запуске) | 🆕 |
disappeared |
Контейнер исчез из docker ps -a |
👻 |
status_changed → не running |
Упал/завис | 🔴 💀 🔄 |
status_changed → running |
Восстановился | ✅ |
health_changed → unhealthy |
Нездоров | 🤒 |
health_changed → healthy |
Оздоровился | ✅ |
State-файл (/tmp/auspex_docker_state.json):
{
"timestamp": "2026-04-14 13:00:00",
"containers": {
"portainer": {"status": "running", "health": "none"},
"zabbix-agent-7.0": {"status": "running", "health": "none"}
}
}
portsview.go — TUI просмотр портов
Точка входа: runPortsView()
Интерактивный TUI на базе Bubble Tea. Показывает занятые порты на хосте (ss -tlnp / netstat). Поддерживает фильтрацию, навигацию клавишами.
Pipeline доставки алертов
Все модули с алертами следуют единому паттерну:
// 1. Telegram
if err := telegramSendHTML(message); err != nil {
log.Printf("❌ Ошибка отправки в Telegram: %v", err)
} else {
log.Println("✅ Алерт успешно отправлен в Telegram")
}
// 2. Email
if err := emailSendHTML("Тема", message); err != nil {
log.Printf("❌ Ошибка отправки Email: %v", err)
} else {
log.Println("✅ Алерт успешно отправлен на Email")
}
// 3. Mattermost
if err := mattermostSend(message); err != nil {
log.Printf("❌ Ошибка отправки в Mattermost: %v", err)
} else {
log.Println("✅ Алерт успешно отправлен в Mattermost")
}
Все три вызова независимы — ошибка одного не влияет на остальные.
Паттерн state-файлов
Модули -p, -sv, -d используют одинаковый механизм для предотвращения спама при запуске по cron:
Запуск N: Загрузить prev_state → Получить curr_state → Compare → Alert if changed → Save curr_state
Запуск N+1: Загрузить prev_state → Получить curr_state → Compare → ...
Первый запуск (state-файл отсутствует): состояние сохраняется, алерты не отправляются. Исключение: в dockercheck появление нового контейнера (dcKindAppeared) тоже не алертится при первом запуске — флаг isFirstRun передаётся в dcCompareStates.
Параллелизм
| Модуль | Механизм |
|---|---|
certcheck |
Пул горутин (cfgMaxWorkers) через канал задач |
servicecheck |
Горутина на каждую службу + sync.WaitGroup |
dockercheck |
Последовательно (inspect на каждый контейнер отдельно) |
portcheck |
Горутина на каждый endpoint |
Работа с PostgreSQL
Модули portcheck и logcheck используют PostgreSQL с failover:
primary (cfgPrimaryDBHost) → ping OK? → использовать
→ ping fail → secondary (cfgSecondaryDBHost)
Каждый модуль реализует свой pcConnectDB() / lcConnectDB() — функции идентичны по логике, префиксы разные для избежания коллизий имён в пакете main.
Соглашения по именованию
Все идентификаторы в каждом модуле имеют уникальный префикс — это позволяет всем файлам жить в одном пакете main без конфликтов:
| Модуль | Префикс |
|---|---|
| certcheck | cc |
| portcheck | pc |
| logcheck | lc |
| matchcheck | mc |
| servicecheck | sc |
| dockercheck | dc |
email |
|
| mattermost | mm |
| config | cfg |