23 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, -w).
Интерактивное меню (showMenu): ASCII-баннер, numbered menu, bufio.Reader для ввода. Поддерживает как цифры (1-8), так и текстовые алиасы (-c, c).
auspex [флаг] [аргументы]
│
├── -ss → runPortsView()
├── -c → runCertCheck()
├── -p → runPortCheck()
├── -m → runMatchCheck(clientFilter)
├── -l → runLogCheck()
├── -sv → runServiceCheck()
├── -d → runDockerCheck()
├── -w → runWebCheck(sid)
├── -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"}
}
}
webcheck.go — проверка доступности сайтов
Точка входа: runWebCheck(sidArg string)
Проверяет HTTP-доступность доменов WAF-клиента через браузер lynx. Использование lynx позволяет обойти антибот-системы которые блокируют стандартные HTTP-клиенты (Go, Python, curl).
Аргумент: один или несколько SID через запятую:
auspex -w 14285
auspex -w 14285,11071,9823
Алгоритм (один SID):
- Проверяет наличие
lynxчерезexec.LookPath— если нет, понятная ошибка с командой установки - Подключается к PostgreSQL (primary → secondary failover) — один раз для всех SID
wcGetDomains— запрашиваетdomain_nameиaliasesизsp_info WHERE sid = $1, затемclient_titleизapps_settings WHERE l7resourceid = $1- Парсит
aliasesиз JSONB-массива ([]string) - Очищает домены через
wcCleanDomain— убирает markdown-ссылки вида[текст](url), протоколы, слеши - Для каждого домена вызывает
wcCheckDomain→wcPrintResult - Выводит итоговую статистику через
wcPrintSummary - Сохраняет CSV-файл
Алгоритм (несколько SID):
При передаче нескольких SID утилита спрашивает способ сохранения CSV:
Несколько SID (3). Сохранить результаты:
1) В один файл (с разделителями по SID)
2) В отдельные файлы для каждого SID
- Вариант 1 — один файл
webcheck_{sid1}_{sid2}_{date}.csvс секциями:
=== SID: 14285 | Название клиента ===
domain,protocol,http_code,available,checked_at
...
=== SID: 11071 | Другой клиент ===
...
- Вариант 2 — отдельный файл для каждого SID как обычно
БД и lynx инициализируются один раз независимо от количества SID.
wcGetDomains — два запроса к БД:
-- 1. Домены клиента
SELECT domain_name, aliases FROM sp_info WHERE sid = $1
-- 2. Название клиента (fallback: sid если не найдено)
SELECT client_title FROM apps_settings WHERE l7resourceid = $1 LIMIT 1
wcCheckDomain:
- Wildcard домены (
*.example.com) — пропускаются немедленно с кодомwildcard - Пробует
https://→ при DNS-ошибке возвращаетdns_errorбез попытки http - Пробует
http://как fallback если https не дал HTTP-кода - Если оба протокола не дали результата →
unavailable
wcLynxGet(url) — ядро проверки:
lynx -head -dump -connect_timeout=10 -read_timeout=10 <url>
-head— HTTP HEAD запрос (только заголовки, без загрузки тела)-dump— неинтерактивный режим, вывод в stdout- Парсит HTTP-код регулярным выражением
HTTP/[\d.]+ (\d{3}) - При редиректах берёт последний код — он актуальный
- DNS-ошибка определяется по строкам
Unable to locate remote hostиCan't access startfileв выводе lynx
Коды результата:
| HTTPCode | Значение |
|---|---|
200, 301, 302... |
HTTP-код ответа сервера |
dns_error |
Домен не резолвится |
wildcard |
Wildcard домен, пропущен |
error |
Оба протокола недоступны |
wcIsSuccessCode — считает успешными коды 2xx и 3xx.
CSV-файл сохраняется в /var/log/auspex/ с UTF-8 BOM для корректного открытия в Excel:
- Один SID:
webcheck_{sid}_{date}.csv - Несколько SID (раздельно):
webcheck_{sid}_{date}.csvдля каждого - Несколько SID (объединённо):
webcheck_{sid1}_{sid2}_..._{date}.csv
Просмотр CSV в терминале:
column -t -s',' /var/log/auspex/webcheck_14285_2026-04-29.csv
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() / wcConnectDB() — функции идентичны по логике, префиксы разные для избежания коллизий имён в пакете main.
webcheck также использует PostgreSQL для получения доменов из таблицы sp_info.
Соглашения по именованию
Все идентификаторы в каждом модуле имеют уникальный префикс — это позволяет всем файлам жить в одном пакете main без конфликтов:
| Модуль | Префикс |
|---|---|
| certcheck | cc |
| portcheck | pc |
| logcheck | lc |
| matchcheck | mc |
| servicecheck | sc |
| dockercheck | dc |
email |
|
| mattermost | mm |
| webcheck | wc |
| config | cfg |