Added docker actions
This commit is contained in:
parent
61af364008
commit
d74a323cdb
15 changed files with 862 additions and 35 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,5 +1,6 @@
|
|||
# Binaries
|
||||
auspex
|
||||
auspex_waf
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
|
|
|
|||
|
|
@ -31,22 +31,24 @@ Auspex — монолитное CLI-приложение на Go. Все мод
|
|||
|
||||
Отвечает за два режима запуска:
|
||||
|
||||
**CLI-флаги** (`runByFlag`): прямой вызов нужного модуля по флагу (`-c`, `-p`, `-l`, `-sv`, `-d`, `-m`, `-ss`, `-w`).
|
||||
**CLI-флаги** (`runByFlag`): прямой вызов нужного модуля по флагу (`-c`, `-p`, `-l`, `-sv`, `-d`, `-m`, `-ss`, `-w`, `-fd`, `-dt`).
|
||||
|
||||
**Интерактивное меню** (`showMenu`): ASCII-баннер, numbered menu, `bufio.Reader` для ввода. Поддерживает как цифры (`1`-`8`), так и текстовые алиасы (`-c`, `c`).
|
||||
**Интерактивное меню** (`showMenu`): ASCII-баннер, numbered menu, `bufio.Reader` для ввода. Поддерживает как цифры (`1`-`10`), так и текстовые алиасы (`-c`, `c`).
|
||||
|
||||
```
|
||||
auspex [флаг] [аргументы]
|
||||
│
|
||||
├── -ss → runPortsView()
|
||||
├── -c → runCertCheck()
|
||||
├── -p → runPortCheck()
|
||||
├── -m → runMatchCheck(clientFilter)
|
||||
├── -l → runLogCheck()
|
||||
├── -p → runPortCheck() [WAF, build tag]
|
||||
├── -m → runMatchCheck(clientFilter) [WAF, build tag]
|
||||
├── -l → runLogCheck() [WAF, build tag]
|
||||
├── -sv → runServiceCheck()
|
||||
├── -d → runDockerCheck()
|
||||
├── -w → runWebCheck(sid)
|
||||
├── -v → версия
|
||||
├── -w → runWebCheck(sid) [WAF, build tag]
|
||||
├── -fd → runFDCheck()
|
||||
├── -dt → runDockerTUI()
|
||||
├── -v → версия (+ вариант сборки: full/generic)
|
||||
└── -h → usage
|
||||
```
|
||||
|
||||
|
|
@ -305,6 +307,56 @@ Endpoint: `POST /api/v4/posts` с заголовком `Authorization: Bearer <t
|
|||
|
||||
---
|
||||
|
||||
### `dockerview.go` — Docker Live TUI (аналог lazydocker)
|
||||
|
||||
**Точка входа:** `runDockerTUI()`
|
||||
|
||||
В отличие от `dockercheck.go` — это интерактивный инструмент прямого действия, а не cron-снапшот с диффом. Переиспользует `dcNormalizeStatus`/`dcGetHealth`/`dcStatusEmoji`/`dcCheckDockerAvailable` из `dockercheck.go` (тот же пакет `main`), не дублируя парсинг статуса.
|
||||
|
||||
**Реализация:** `bubbletea`/`bubbles`/`lipgloss` (те же зависимости, что и в `portsview.go`, новых пакетов в `go.mod` не добавляет — `viewport` уже часть модуля `bubbles`).
|
||||
|
||||
**Автообновление:** список контейнеров (`docker ps -a` + `docker inspect` на health) обновляется раз в 3 сек, если включено (`a`).
|
||||
|
||||
**Действия:**
|
||||
|
||||
| Клавиша | Действие | Механизм |
|
||||
|---|---|---|
|
||||
| `l` | Логи | `docker logs -f --tail 200 <name>`, терминал передаётся через `tea.ExecProcess` |
|
||||
| `e` | Шелл в контейнер | `docker exec -it <name> sh -c 'exec bash \|\| exec sh'`, тоже через `tea.ExecProcess` |
|
||||
| `i` | `docker inspect` | JSON форматируется через `encoding/json.Indent`, показывается в прокручиваемом `viewport` |
|
||||
| `s`/`t`/`R`/`d` | stop/start/restart/remove (`rm -f`) | Через диалог подтверждения (`y`/`n`), показывающий точную команду |
|
||||
| `r` / `a` | Ручной рефреш / автообновление | — |
|
||||
|
||||
`tea.ExecProcess` — ключевой механизм для логов и шелла: приостанавливает рендеринг Bubble Tea, отдаёт реальный терминал дочернему процессу (полноценный интерактивный сеанс — ввод, TTY), и восстанавливает TUI после его завершения. Работает только в интерактивном терминале (не через cron/pipe).
|
||||
|
||||
---
|
||||
|
||||
### `fdcheck.go` — удалённые-но-открытые файловые дескрипторы
|
||||
|
||||
**Точка входа:** `runFDCheck()`
|
||||
|
||||
Находит ситуации "место не освобождается после `rm`, потому что кто-то держит файл открытым".
|
||||
|
||||
**Алгоритм:**
|
||||
1. Обход `/proc/[pid]/fd/*` по всем процессам, `os.Readlink` на каждый — ищем таргеты с суффиксом `" (deleted)"`
|
||||
2. Фильтрация шума: memfd, anon_inode, сокеты/пайпы, всё вне обычной ФС — не считаются утечкой места
|
||||
3. Размер — через `os.Stat` на `/proc/PID/fd/N` (следует по magic-symlink к живому, хоть и удалённому, инoду)
|
||||
4. Таблица отсортирована по размеру, интерактивный CLI-выбор записи → меню действий
|
||||
|
||||
**Действия:**
|
||||
|
||||
| Действие | Механизм | Примечание |
|
||||
|---|---|---|
|
||||
| Recover | `io.Copy` из `/proc/PID/fd/N` в новый файл | Снимок данных, места не освобождает |
|
||||
| Reopen (SIGHUP/SIGUSR1) | `syscall.Kill(pid, sig)` | Работает только если демон сам поддерживает reopen (nginx/angie/rsyslog) |
|
||||
| Restart | `systemctl restart <unit>` | `unit` определяется через regex по `/proc/PID/cgroup` |
|
||||
| Kill | SIGTERM → (3 сек) → SIGKILL | Освобождает место немедленно |
|
||||
| Truncate на месте | `os.OpenFile(fdPath, O_WRONLY).Truncate(0)` | Тот же приём, что `logrotate copytruncate`; безопасно только для `O_APPEND`-файлов — режим открытия проверяется через `/proc/PID/fdinfo/N` (`flags:`, бит `O_APPEND`) |
|
||||
|
||||
Truncate — единственное действие, которое освобождает место **без рестарта демона**: `ftruncate` меняет размер inode, а не конкретный fd, поэтому воздействует на все файловые описания, ссылающиеся на тот же inode, включая fd исходного процесса.
|
||||
|
||||
---
|
||||
|
||||
### `webcheck.go` — проверка доступности сайтов
|
||||
|
||||
**Точка входа:** `runWebCheck(sidArg string)`
|
||||
|
|
@ -486,7 +538,21 @@ primary (cfgPrimaryDBHost) → ping OK? → использовать
|
|||
| matchcheck | `mc` |
|
||||
| servicecheck | `sc` |
|
||||
| dockercheck | `dc` |
|
||||
| dockerview | `dv` |
|
||||
| fdcheck | `fd` |
|
||||
| email | `email` |
|
||||
| mattermost | `mm` |
|
||||
| webcheck | `wc` |
|
||||
| config | `cfg` |
|
||||
|
||||
---
|
||||
|
||||
## Build tags: full vs generic
|
||||
|
||||
`logcheck.go`, `webcheck.go`, `portcheck.go`, `matchcheck.go` требуют БД `waf_info` и/или структуру каталогов PT AF — они помечены `//go:build waf` и компилируются только при `go build -tags waf .`.
|
||||
|
||||
Для generic-сборки (`go build .`, без тега) их заменяют файлы `*_stub.go` с обратным ограничением `//go:build !waf` — те же имена и сигнатуры функций (`runLogCheck`, `runWebCheck`, `runPortCheck`, `runMatchCheck`), но вместо логики — `fmt.Errorf` с подсказкой пересобрать с нужным тегом. Благодаря этому `main.go` не содержит условной компиляции и одинаково линкуется в обоих вариантах.
|
||||
|
||||
`buildinfo_waf.go` / `buildinfo_generic.go` по той же схеме задают константу `buildVariant` (`"full"` / `"generic"`), которую показывают баннер меню и `auspex -v`.
|
||||
|
||||
`lib/pq` используется только в трёх WAF-тегированных файлах — в generic-сборке он просто не компилируется (в `go.mod` остаётся объявленным, это нормально для двух конфигураций одного модуля; не стоит гонять `go mod tidy` без тега `waf`, иначе он может решить, что зависимость лишняя).
|
||||
66
README.md
Executable file → Normal file
66
README.md
Executable file → Normal file
|
|
@ -8,40 +8,69 @@
|
|||
|------|----------|
|
||||
| `-ss` | Просмотр занятых портов (интерактивный TUI на bubbletea) |
|
||||
| `-c` | Проверка SSL сертификатов (сроки истечения, алерты) |
|
||||
| `-p` | Проверка доступности портов из nginx upstream конфигов |
|
||||
| `-m` | Проверка совпадений портов Angie ↔ Docker (+ проверка запуска контейнеров) |
|
||||
| `-l` | Проверка обновления файлов PT AF access logs |
|
||||
| `-p` | Проверка доступности портов из nginx upstream конфигов **[WAF]** |
|
||||
| `-m` | Проверка совпадений портов Angie ↔ Docker (+ проверка запуска контейнеров) **[WAF]** |
|
||||
| `-l` | Проверка обновления файлов PT AF access logs **[WAF]** |
|
||||
| `-sv` | Проверка работоспособности служб systemd |
|
||||
| `-d` или `-dc` | Проверка работоспособности Docker контейнеров |
|
||||
| `-d` или `-dc` | Проверка работоспособности Docker контейнеров (снапшот состояния + алерты при изменениях) |
|
||||
| `-w` | Проверка доступности сайтов клиента через lynx **[WAF]** |
|
||||
| `-fd` | Поиск удалённых-но-открытых файловых дескрипторов (recover/reopen/truncate/restart/kill) |
|
||||
| `-dt` | Docker Live — интерактивный TUI (аналог lazydocker): логи, шелл, stop/start/restart/remove |
|
||||
|
||||
Модули **[WAF]** требуют БД `waf_info` и/или структуру каталогов PT AF и доступны
|
||||
только в сборке `full` (см. [Сборка](#сборка)).
|
||||
|
||||
## Структура проекта
|
||||
|
||||
```
|
||||
auspex/
|
||||
├── main.go # Точка входа, меню, роутинг CLI-флагов
|
||||
├── config.go # Глобальная конфигурация (переменные + загрузка из .env)
|
||||
├── telegram.go # Отправка алертов в Telegram (поддержка HTTP и SOCKS5 прокси)
|
||||
├── email.go # Отправка алертов по Email (SMTP, AUTH LOGIN)
|
||||
├── mattermost.go # Отправка алертов в Mattermost (Bot API)
|
||||
├── certcheck.go # -c: проверка SSL сертификатов
|
||||
├── portcheck.go # -p: проверка доступности портов
|
||||
├── matchcheck.go # -m: совпадения Angie ↔ Docker
|
||||
├── logcheck.go # -l: проверка свежести логов
|
||||
├── servicecheck.go # -sv: проверка служб systemd
|
||||
├── dockercheck.go # -d: проверка Docker контейнеров
|
||||
├── portsview.go # -ss: TUI просмотр занятых портов
|
||||
├── main.go # Точка входа, меню, роутинг CLI-флагов
|
||||
├── config.go # Глобальная конфигурация (переменные + загрузка из .env)
|
||||
├── telegram.go # Отправка алертов в Telegram (поддержка HTTP и SOCKS5 прокси)
|
||||
├── email.go # Отправка алертов по Email (SMTP, AUTH LOGIN)
|
||||
├── mattermost.go # Отправка алертов в Mattermost (Bot API)
|
||||
├── certcheck.go # -c: проверка SSL сертификатов
|
||||
├── servicecheck.go # -sv: проверка служб systemd
|
||||
├── dockercheck.go # -d: проверка Docker контейнеров (снапшот + diff)
|
||||
├── dockerview.go # -dt: Docker Live TUI (аналог lazydocker)
|
||||
├── portsview.go # -ss: TUI просмотр занятых портов
|
||||
├── fdcheck.go # -fd: удалённые-но-открытые файловые дескрипторы
|
||||
├── portcheck.go # -p: проверка доступности портов [WAF, build tag]
|
||||
├── portcheck_stub.go # заглушка -p для generic-сборки [!WAF]
|
||||
├── matchcheck.go # -m: совпадения Angie ↔ Docker [WAF, build tag]
|
||||
├── matchcheck_stub.go # заглушка -m для generic-сборки [!WAF]
|
||||
├── logcheck.go # -l: проверка свежести логов [WAF, build tag]
|
||||
├── logcheck_stub.go # заглушка -l для generic-сборки [!WAF]
|
||||
├── webcheck.go # -w: проверка доступности сайтов (lynx) [WAF, build tag]
|
||||
├── webcheck_stub.go # заглушка -w для generic-сборки [!WAF]
|
||||
├── buildinfo_waf.go # buildVariant = "full" [build tag: waf]
|
||||
├── buildinfo_generic.go# buildVariant = "generic" [build tag: !waf]
|
||||
├── go.mod
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Сборка
|
||||
|
||||
Два варианта сборки через Go build tags:
|
||||
|
||||
```bash
|
||||
go mod tidy
|
||||
|
||||
# generic — только модули без зависимости от WAF/PT AF: -c -sv -dc -ss -fd -dt
|
||||
go build -o auspex .
|
||||
|
||||
# full — все модули, включая -p -m -l -w (требуют БД waf_info / структуру PT AF)
|
||||
go build -tags waf -o auspex .
|
||||
|
||||
sudo cp auspex /usr/local/bin/auspex
|
||||
```
|
||||
|
||||
Файлы `logcheck.go`, `webcheck.go`, `portcheck.go`, `matchcheck.go` помечены
|
||||
`//go:build waf` и не попадают в generic-сборку; вместо них компилируются
|
||||
заглушки (`*_stub.go`), которые при вызове `-l`/`-w`/`-p`/`-m` выводят понятную
|
||||
ошибку с подсказкой пересобрать с `-tags waf`. Какая сборка используется —
|
||||
видно в баннере меню и в выводе `auspex -v` (`full`/`generic`).
|
||||
|
||||
## Использование
|
||||
|
||||
```bash
|
||||
|
|
@ -58,6 +87,8 @@ sudo cp auspex /usr/local/bin/auspex
|
|||
./auspex -sv # Проверка работоспособности служб
|
||||
./auspex -d # Проверка работоспособности контейнеров
|
||||
./auspex -w # Проверка веб доступности
|
||||
./auspex -fd # Поиск удалённых-но-открытых файловых дескрипторов
|
||||
./auspex -dt # Docker Live TUI (аналог lazydocker)
|
||||
./auspex -h # Справка
|
||||
./auspex -v # Версия
|
||||
```
|
||||
|
|
@ -132,6 +163,7 @@ sudo nano /etc/auspex/auspex.env
|
|||
| `STATE_FILE_PATH` | State-файл portcheck | `/tmp/port_checker_state.json` |
|
||||
| `SERVICE_STATE_FILE_PATH` | State-файл servicecheck | `/tmp/auspex_service_state.json` |
|
||||
| `DOCKER_STATE_FILE_PATH` | State-файл dockercheck | `/tmp/auspex_docker_state.json` |
|
||||
| `FD_RECOVER_DIR` | Куда сохраняются снимки восстановленных файлов (`-fd`) | `/var/tmp/auspex-recovered` |
|
||||
|
||||
#### Пороги сертификатов
|
||||
|
||||
|
|
@ -251,4 +283,4 @@ CERT_INFO_DAYS=30
|
|||
|
||||
## Подробнее
|
||||
|
||||
См. [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
См. [ARCHITECTURE.md](ARCHITECTURE.md).
|
||||
6
buildinfo_generic.go
Normal file
6
buildinfo_generic.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
//go:build !waf
|
||||
|
||||
package main
|
||||
|
||||
const buildHasWAF = false
|
||||
const buildVariant = "generic"
|
||||
6
buildinfo_waf.go
Normal file
6
buildinfo_waf.go
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
//go:build waf
|
||||
|
||||
package main
|
||||
|
||||
const buildHasWAF = true
|
||||
const buildVariant = "full"
|
||||
632
dockerview.go
Normal file
632
dockerview.go
Normal file
|
|
@ -0,0 +1,632 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/charmbracelet/bubbles/table"
|
||||
"github.com/charmbracelet/bubbles/textinput"
|
||||
"github.com/charmbracelet/bubbles/viewport"
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
)
|
||||
|
||||
// =============================================================================
|
||||
// dockerview — live TUI для Docker-контейнеров (аналог lazydocker)
|
||||
// =============================================================================
|
||||
//
|
||||
// В отличие от dockercheck.go (-d/-dc, снапшот + диффы состояния для cron),
|
||||
// этот модуль — интерактивный, с автообновлением и прямыми действиями:
|
||||
// stop/start/restart/remove, просмотр логов (docker logs -f) и подключение
|
||||
// к шеллу контейнера (docker exec -it), плюс просмотр docker inspect.
|
||||
//
|
||||
// Переиспользует dcNormalizeStatus/dcGetHealth/dcStatusEmoji из dockercheck.go
|
||||
// (тот же пакет main) — не дублируем парсинг статуса.
|
||||
// =============================================================================
|
||||
|
||||
// ── Типы ─────────────────────────────────────────────────────────────────────
|
||||
|
||||
type dvContainerEntry struct {
|
||||
ID string
|
||||
Name string
|
||||
Image string
|
||||
Status string
|
||||
Health string
|
||||
Ports string
|
||||
}
|
||||
|
||||
type dvLoadedMsg struct {
|
||||
entries []dvContainerEntry
|
||||
err error
|
||||
}
|
||||
|
||||
type dvAutoTickMsg struct{}
|
||||
|
||||
type dvActionResultMsg struct {
|
||||
action string
|
||||
target string
|
||||
output string
|
||||
err error
|
||||
}
|
||||
|
||||
type dvInspectMsg struct {
|
||||
content string
|
||||
err error
|
||||
}
|
||||
|
||||
type dvExecFinishedMsg struct {
|
||||
action string
|
||||
err error
|
||||
}
|
||||
|
||||
type dvModel struct {
|
||||
table table.Model
|
||||
textInput textinput.Model
|
||||
viewport viewport.Model
|
||||
|
||||
entries []dvContainerEntry
|
||||
filtered []dvContainerEntry
|
||||
|
||||
err error
|
||||
status string
|
||||
|
||||
width, height int
|
||||
|
||||
isFiltering bool
|
||||
loading bool
|
||||
autoRefresh bool
|
||||
lastRefresh time.Time
|
||||
|
||||
showInspect bool
|
||||
loadingInspect bool
|
||||
|
||||
confirmAction string // "stop" | "start" | "restart" | "remove"
|
||||
confirmTarget dvContainerEntry
|
||||
}
|
||||
|
||||
// ── Стили ────────────────────────────────────────────────────────────────────
|
||||
|
||||
var (
|
||||
dvBaseStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#6c7086")).
|
||||
Padding(1, 2)
|
||||
|
||||
dvLogoStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#89b4fa")).
|
||||
Padding(0, 1)
|
||||
|
||||
dvHelpStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#6c7086")).
|
||||
Padding(0, 1)
|
||||
|
||||
dvStatusStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#a6e3a1")).
|
||||
Bold(true)
|
||||
|
||||
dvErrorStatusStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#f38ba8")).
|
||||
Bold(true)
|
||||
|
||||
dvInputStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#89b4fa")).
|
||||
Padding(0, 1)
|
||||
|
||||
dvDetailsStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#89b4fa")).
|
||||
Padding(1, 2).
|
||||
Width(100)
|
||||
|
||||
dvDetailsTitleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#89b4fa")).
|
||||
Padding(0, 0, 1, 0)
|
||||
|
||||
dvConfirmStyle = lipgloss.NewStyle().
|
||||
BorderStyle(lipgloss.RoundedBorder()).
|
||||
BorderForeground(lipgloss.Color("#f38ba8")).
|
||||
Padding(1, 3)
|
||||
|
||||
dvConfirmTitleStyle = lipgloss.NewStyle().
|
||||
Bold(true).
|
||||
Foreground(lipgloss.Color("#f38ba8")).
|
||||
Padding(0, 0, 1, 0)
|
||||
|
||||
dvLoadingStyle = lipgloss.NewStyle().
|
||||
Foreground(lipgloss.Color("#f9e2af")).
|
||||
Bold(true)
|
||||
)
|
||||
|
||||
// ── Точка входа ──────────────────────────────────────────────────────────────
|
||||
|
||||
func runDockerTUI() error {
|
||||
if err := dcCheckDockerAvailable(); err != nil {
|
||||
return fmt.Errorf("docker недоступен: %v", err)
|
||||
}
|
||||
|
||||
columns := []table.Column{
|
||||
{Title: "Имя", Width: 22},
|
||||
{Title: "Статус", Width: 14},
|
||||
{Title: "Health", Width: 10},
|
||||
{Title: "Образ", Width: 26},
|
||||
{Title: "Порты", Width: 22},
|
||||
{Title: "ID", Width: 12},
|
||||
}
|
||||
|
||||
t := table.New(
|
||||
table.WithColumns(columns),
|
||||
table.WithFocused(true),
|
||||
table.WithHeight(12),
|
||||
)
|
||||
|
||||
s := table.DefaultStyles()
|
||||
s.Header = s.Header.
|
||||
BorderStyle(lipgloss.NormalBorder()).
|
||||
BorderForeground(lipgloss.Color("#6c7086")).
|
||||
BorderBottom(true).
|
||||
Bold(true)
|
||||
s.Selected = s.Selected.
|
||||
Foreground(lipgloss.Color("#cdd6f4")).
|
||||
Background(lipgloss.Color("#313244")).
|
||||
Bold(false)
|
||||
t.SetStyles(s)
|
||||
|
||||
ti := textinput.New()
|
||||
ti.Placeholder = "Поиск по имени, образу, статусу..."
|
||||
ti.CharLimit = 156
|
||||
ti.Width = 40
|
||||
|
||||
vp := viewport.New(96, 20)
|
||||
|
||||
m := dvModel{
|
||||
table: t,
|
||||
textInput: ti,
|
||||
viewport: vp,
|
||||
autoRefresh: true,
|
||||
}
|
||||
|
||||
if _, err := tea.NewProgram(m, tea.WithAltScreen()).Run(); err != nil {
|
||||
return fmt.Errorf("ошибка запуска TUI: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ── Tea Model ────────────────────────────────────────────────────────────────
|
||||
|
||||
func (m dvModel) Init() tea.Cmd {
|
||||
return tea.Batch(dvLoadAsync, textinput.Blink, dvTickAuto)
|
||||
}
|
||||
|
||||
func dvTickAuto() tea.Msg {
|
||||
time.Sleep(3 * time.Second)
|
||||
return dvAutoTickMsg{}
|
||||
}
|
||||
|
||||
func dvLoadAsync() tea.Msg {
|
||||
entries, err := dvGetContainers()
|
||||
return dvLoadedMsg{entries: entries, err: err}
|
||||
}
|
||||
|
||||
func (m dvModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||
var cmd tea.Cmd
|
||||
|
||||
// Автообновление — работает в фоне независимо от текущего режима.
|
||||
if _, ok := msg.(dvAutoTickMsg); ok {
|
||||
var cmds []tea.Cmd
|
||||
if m.autoRefresh && !m.loading && time.Since(m.lastRefresh) > 3*time.Second {
|
||||
cmds = append(cmds, dvLoadAsync)
|
||||
}
|
||||
cmds = append(cmds, dvTickAuto)
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// ── Просмотр docker inspect ──────────────────────────────────────────
|
||||
if m.showInspect {
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
if msg.String() == "esc" || msg.String() == "q" {
|
||||
m.showInspect = false
|
||||
m.loadingInspect = false
|
||||
return m, nil
|
||||
}
|
||||
case dvInspectMsg:
|
||||
m.loadingInspect = false
|
||||
if msg.err != nil {
|
||||
m.viewport.SetContent(fmt.Sprintf("Ошибка: %v", msg.err))
|
||||
} else {
|
||||
m.viewport.SetContent(msg.content)
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
m.viewport, cmd = m.viewport.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
// ── Диалог подтверждения (stop/start/restart/remove) ─────────────────
|
||||
if m.confirmAction != "" {
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
switch keyMsg.String() {
|
||||
case "y", "Y", "enter":
|
||||
action := m.confirmAction
|
||||
target := m.confirmTarget
|
||||
m.confirmAction = ""
|
||||
m.status = fmt.Sprintf("⏳ %s: %s...", action, target.Name)
|
||||
return m, dvRunAction(action, target.Name)
|
||||
case "n", "N", "esc":
|
||||
m.status = "Отменено"
|
||||
m.confirmAction = ""
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// ── Строка поиска ─────────────────────────────────────────────────────
|
||||
if m.isFiltering {
|
||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||
if keyMsg.String() == "enter" || keyMsg.String() == "esc" {
|
||||
m.isFiltering = false
|
||||
m.table.Focus()
|
||||
return m, nil
|
||||
}
|
||||
}
|
||||
m.textInput, cmd = m.textInput.Update(msg)
|
||||
m.dvFilter()
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
switch msg := msg.(type) {
|
||||
case tea.KeyMsg:
|
||||
switch msg.String() {
|
||||
case "q", "ctrl+c":
|
||||
return m, tea.Quit
|
||||
|
||||
case "/":
|
||||
m.isFiltering = true
|
||||
m.textInput.Focus()
|
||||
m.textInput.SetValue("")
|
||||
return m, textinput.Blink
|
||||
|
||||
case "r":
|
||||
if !m.loading {
|
||||
m.status = "Обновляю..."
|
||||
m.loading = true
|
||||
return m, dvLoadAsync
|
||||
}
|
||||
|
||||
case "a":
|
||||
m.autoRefresh = !m.autoRefresh
|
||||
if m.autoRefresh {
|
||||
m.status = "Автообновление: включено"
|
||||
} else {
|
||||
m.status = "Автообновление: выключено"
|
||||
}
|
||||
|
||||
case "s": // stop
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
m.confirmAction = "stop"
|
||||
m.confirmTarget = e
|
||||
}
|
||||
|
||||
case "t": // start
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
m.confirmAction = "start"
|
||||
m.confirmTarget = e
|
||||
}
|
||||
|
||||
case "R": // restart (заглавная, чтобы не конфликтовать с "r" = refresh)
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
m.confirmAction = "restart"
|
||||
m.confirmTarget = e
|
||||
}
|
||||
|
||||
case "d": // remove
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
m.confirmAction = "remove"
|
||||
m.confirmTarget = e
|
||||
}
|
||||
|
||||
case "l": // logs -f (передаём терминал docker logs)
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
return m, dvExecLogs(e.Name)
|
||||
}
|
||||
|
||||
case "e": // shell в контейнер
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
return m, dvExecShell(e.Name)
|
||||
}
|
||||
|
||||
case "i": // docker inspect
|
||||
if e, ok := m.dvSelected(); ok {
|
||||
m.showInspect = true
|
||||
m.loadingInspect = true
|
||||
m.viewport.SetContent("Загрузка...")
|
||||
return m, dvInspectCmd(e.Name)
|
||||
}
|
||||
}
|
||||
|
||||
case tea.WindowSizeMsg:
|
||||
m.width = msg.Width
|
||||
m.height = msg.Height
|
||||
avail := m.height - 7
|
||||
dvBaseStyle = dvBaseStyle.Width(m.width - 2).Height(avail)
|
||||
m.table.SetWidth(m.width - 4)
|
||||
th := avail - 2
|
||||
if th < 2 {
|
||||
th = 2
|
||||
}
|
||||
m.table.SetHeight(th)
|
||||
m.viewport.Width = m.width - 8
|
||||
m.viewport.Height = avail - 2
|
||||
|
||||
case dvLoadedMsg:
|
||||
m.loading = false
|
||||
m.lastRefresh = time.Now()
|
||||
if msg.err != nil {
|
||||
m.err = msg.err
|
||||
} else {
|
||||
m.entries = msg.entries
|
||||
m.err = nil
|
||||
m.dvFilter()
|
||||
if m.status == "Обновляю..." {
|
||||
m.status = "Обновлено"
|
||||
}
|
||||
}
|
||||
|
||||
case dvActionResultMsg:
|
||||
if msg.err != nil {
|
||||
m.status = fmt.Sprintf("❌ %s (%s): %v — %s", msg.action, msg.target, msg.err, msg.output)
|
||||
} else {
|
||||
m.status = fmt.Sprintf("✅ %s (%s) выполнено", msg.action, msg.target)
|
||||
}
|
||||
return m, dvLoadAsync
|
||||
|
||||
case dvExecFinishedMsg:
|
||||
switch msg.action {
|
||||
case "logs":
|
||||
m.status = "Просмотр логов завершён"
|
||||
case "exec":
|
||||
if msg.err != nil {
|
||||
m.status = "Шелл завершён с ошибкой (см. вывод выше)"
|
||||
} else {
|
||||
m.status = "Шелл закрыт"
|
||||
}
|
||||
}
|
||||
return m, dvLoadAsync
|
||||
|
||||
case error:
|
||||
m.err = msg
|
||||
m.loading = false
|
||||
}
|
||||
|
||||
m.table, cmd = m.table.Update(msg)
|
||||
return m, cmd
|
||||
}
|
||||
|
||||
func (m *dvModel) dvSelected() (dvContainerEntry, bool) {
|
||||
idx := m.table.Cursor()
|
||||
if idx < 0 || idx >= len(m.filtered) {
|
||||
return dvContainerEntry{}, false
|
||||
}
|
||||
return m.filtered[idx], true
|
||||
}
|
||||
|
||||
func (m *dvModel) dvFilter() {
|
||||
q := strings.ToLower(m.textInput.Value())
|
||||
m.filtered = nil
|
||||
for _, e := range m.entries {
|
||||
if q == "" ||
|
||||
strings.Contains(strings.ToLower(e.Name), q) ||
|
||||
strings.Contains(strings.ToLower(e.Image), q) ||
|
||||
strings.Contains(strings.ToLower(e.Status), q) ||
|
||||
strings.Contains(strings.ToLower(e.Ports), q) {
|
||||
m.filtered = append(m.filtered, e)
|
||||
}
|
||||
}
|
||||
m.dvUpdateTable()
|
||||
}
|
||||
|
||||
func (m *dvModel) dvUpdateTable() {
|
||||
rows := []table.Row{}
|
||||
for _, e := range m.filtered {
|
||||
icon := dcStatusEmoji(e.Status, e.Health)
|
||||
health := e.Health
|
||||
if health == "" {
|
||||
health = "none"
|
||||
}
|
||||
rows = append(rows, table.Row{
|
||||
e.Name, icon + " " + e.Status, health, e.Image, e.Ports, e.ID,
|
||||
})
|
||||
}
|
||||
m.table.SetRows(rows)
|
||||
}
|
||||
|
||||
// ── Отрисовка ────────────────────────────────────────────────────────────────
|
||||
|
||||
func (m dvModel) View() string {
|
||||
if m.err != nil {
|
||||
return fmt.Sprintf("Ошибка: %v\nНажмите 'q' для выхода", m.err)
|
||||
}
|
||||
|
||||
if m.showInspect {
|
||||
title := dvDetailsTitleStyle.Render("🔍 docker inspect")
|
||||
body := title + "\n"
|
||||
if m.loadingInspect {
|
||||
body += dvLoadingStyle.Render("Загрузка...") + "\n"
|
||||
} else {
|
||||
body += m.viewport.View()
|
||||
}
|
||||
body += "\n\n" + dvHelpStyle.Render("↑/↓ PgUp/PgDn: Прокрутка • Esc/q: Закрыть")
|
||||
box := dvDetailsStyle.Render(body)
|
||||
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, box)
|
||||
}
|
||||
|
||||
if m.confirmAction != "" {
|
||||
box := dvConfirmStyle.Render(dvConfirmMessage(m.confirmAction, m.confirmTarget))
|
||||
return lipgloss.Place(m.width, m.height, lipgloss.Center, lipgloss.Center, box)
|
||||
}
|
||||
|
||||
logo := dvLogoStyle.Render("🐳 Auspex — Docker Live")
|
||||
if m.loading {
|
||||
logo += " " + dvLoadingStyle.Render("●")
|
||||
}
|
||||
if m.autoRefresh {
|
||||
logo += " " + dvHelpStyle.Render("[AUTO]")
|
||||
}
|
||||
|
||||
tableView := dvBaseStyle.Render(m.table.View())
|
||||
|
||||
controls := "↑/↓: Навигация • /: Поиск • l: Логи • e: Шелл • i: Inspect • " +
|
||||
"s: Stop • t: Start • R: Restart • d: Remove • r: Refresh • a: Auto • q: Выход"
|
||||
|
||||
if m.isFiltering {
|
||||
inputView := dvInputStyle.Render(m.textInput.View())
|
||||
return fmt.Sprintf("%s\n%s\n%s\n%s", logo, tableView, inputView,
|
||||
dvHelpStyle.Render("Введите текст для поиска • Esc/Enter: Готово"))
|
||||
}
|
||||
|
||||
status := m.status
|
||||
renderedControls := dvHelpStyle.Render(controls)
|
||||
if status != "" {
|
||||
style := dvStatusStyle
|
||||
if strings.HasPrefix(status, "❌") {
|
||||
style = dvErrorStatusStyle
|
||||
}
|
||||
renderedControls = style.Render(status) + " • " + renderedControls
|
||||
}
|
||||
|
||||
return fmt.Sprintf("%s\n%s\n%s", logo, tableView, renderedControls)
|
||||
}
|
||||
|
||||
func dvConfirmMessage(action string, t dvContainerEntry) string {
|
||||
var verb, cmdStr string
|
||||
switch action {
|
||||
case "stop":
|
||||
verb, cmdStr = "ОСТАНОВИТЬ", fmt.Sprintf("docker stop %s", t.Name)
|
||||
case "start":
|
||||
verb, cmdStr = "ЗАПУСТИТЬ", fmt.Sprintf("docker start %s", t.Name)
|
||||
case "restart":
|
||||
verb, cmdStr = "ПЕРЕЗАПУСТИТЬ", fmt.Sprintf("docker restart %s", t.Name)
|
||||
case "remove":
|
||||
verb, cmdStr = "УДАЛИТЬ (force)", fmt.Sprintf("docker rm -f %s", t.Name)
|
||||
}
|
||||
|
||||
title := dvConfirmTitleStyle.Render(fmt.Sprintf("⚠️ %s контейнер?", verb))
|
||||
return fmt.Sprintf("%s\nКонтейнер: %s\nКоманда: %s\n\n%s",
|
||||
title, t.Name, cmdStr, dvHelpStyle.Render("y: Да • n/Esc: Отмена"))
|
||||
}
|
||||
|
||||
// ── Системные функции ────────────────────────────────────────────────────────
|
||||
|
||||
// dvGetContainers получает список контейнеров с портами.
|
||||
// Health переиспользует dcGetHealth, статус — dcNormalizeStatus (dockercheck.go).
|
||||
func dvGetContainers() ([]dvContainerEntry, error) {
|
||||
out, err := exec.Command(
|
||||
"docker", "ps", "-a",
|
||||
"--format", "{{.ID}}|{{.Names}}|{{.Status}}|{{.Image}}|{{.Ports}}",
|
||||
).CombinedOutput()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("docker ps: %v\n%s", err, string(out))
|
||||
}
|
||||
|
||||
var entries []dvContainerEntry
|
||||
for _, line := range strings.Split(strings.TrimSpace(string(out)), "\n") {
|
||||
if line == "" {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(line, "|", 5)
|
||||
if len(parts) < 4 {
|
||||
continue
|
||||
}
|
||||
|
||||
e := dvContainerEntry{
|
||||
ID: strings.TrimSpace(parts[0]),
|
||||
Name: strings.TrimSpace(parts[1]),
|
||||
Status: dcNormalizeStatus(parts[2]),
|
||||
Image: strings.TrimSpace(parts[3]),
|
||||
}
|
||||
if len(parts) == 5 {
|
||||
e.Ports = strings.TrimSpace(parts[4])
|
||||
}
|
||||
entries = append(entries, e)
|
||||
}
|
||||
|
||||
for i := range entries {
|
||||
entries[i].Health = dcGetHealth(entries[i].ID)
|
||||
}
|
||||
|
||||
sort.Slice(entries, func(i, j int) bool {
|
||||
return strings.ToLower(entries[i].Name) < strings.ToLower(entries[j].Name)
|
||||
})
|
||||
|
||||
return entries, nil
|
||||
}
|
||||
|
||||
// dvRunAction выполняет docker stop/start/restart/rm -f и возвращает результат.
|
||||
func dvRunAction(action, name string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
var cmd *exec.Cmd
|
||||
switch action {
|
||||
case "stop":
|
||||
cmd = exec.Command("docker", "stop", name)
|
||||
case "start":
|
||||
cmd = exec.Command("docker", "start", name)
|
||||
case "restart":
|
||||
cmd = exec.Command("docker", "restart", name)
|
||||
case "remove":
|
||||
cmd = exec.Command("docker", "rm", "-f", name)
|
||||
default:
|
||||
return dvActionResultMsg{action: action, target: name, err: fmt.Errorf("неизвестное действие")}
|
||||
}
|
||||
|
||||
out, err := cmd.CombinedOutput()
|
||||
return dvActionResultMsg{
|
||||
action: action, target: name,
|
||||
output: strings.TrimSpace(string(out)), err: err,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dvInspectCmd получает docker inspect и красиво форматирует JSON.
|
||||
func dvInspectCmd(name string) tea.Cmd {
|
||||
return func() tea.Msg {
|
||||
out, err := exec.Command("docker", "inspect", name).Output()
|
||||
if err != nil {
|
||||
return dvInspectMsg{err: fmt.Errorf("docker inspect: %w", err)}
|
||||
}
|
||||
|
||||
var pretty bytes.Buffer
|
||||
if err := json.Indent(&pretty, out, "", " "); err != nil {
|
||||
return dvInspectMsg{content: string(out)} // fallback — как есть
|
||||
}
|
||||
return dvInspectMsg{content: pretty.String()}
|
||||
}
|
||||
}
|
||||
|
||||
// dvExecLogs передаёт терминал `docker logs -f` — TUI приостанавливается на время
|
||||
// просмотра, возврат в TUI происходит по Ctrl+C (завершает logs) или выходу.
|
||||
func dvExecLogs(name string) tea.Cmd {
|
||||
c := exec.Command("docker", "logs", "-f", "--tail", "200", name)
|
||||
return tea.ExecProcess(c, func(err error) tea.Msg {
|
||||
return dvExecFinishedMsg{action: "logs", err: err}
|
||||
})
|
||||
}
|
||||
|
||||
// dvExecShell аналогично передаёт терминал интерактивному шеллу внутри контейнера.
|
||||
// Пробует bash, если недоступен — падает обратно на sh (стандартный подход
|
||||
// для минимальных образов вроде alpine).
|
||||
func dvExecShell(name string) tea.Cmd {
|
||||
shellProbe := `if command -v bash >/dev/null 2>&1; then exec bash; else exec sh; fi`
|
||||
c := exec.Command("docker", "exec", "-it", name, "sh", "-c", shellProbe)
|
||||
return tea.ExecProcess(c, func(err error) tea.Msg {
|
||||
return dvExecFinishedMsg{action: "exec", err: err}
|
||||
})
|
||||
}
|
||||
2
logcheck.go
Executable file → Normal file
2
logcheck.go
Executable file → Normal file
|
|
@ -1,3 +1,5 @@
|
|||
//go:build waf
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
|||
13
logcheck_stub.go
Normal file
13
logcheck_stub.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//go:build !waf
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Заглушка для сборки без тега 'waf'. Реальная реализация — в logcheck.go
|
||||
// (собирается только с 'go build -tags waf .'), т.к. модуль требует БД waf_info
|
||||
// и специфичной для PT AF структуры путей (/var/log/ptaf_nginx/...).
|
||||
func runLogCheck() error {
|
||||
return fmt.Errorf("модуль -l (logcheck) недоступен в этой сборке: требует БД waf_info.\n" +
|
||||
"Пересоберите бинарник с 'go build -tags waf .', чтобы получить этот модуль")
|
||||
}
|
||||
45
main.go
Executable file → Normal file
45
main.go
Executable file → Normal file
|
|
@ -111,8 +111,14 @@ func runByFlag(flag string, extraArgs []string) {
|
|||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "-dt":
|
||||
if err := runDockerTUI(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
case "-v", "--version":
|
||||
fmt.Printf("auspex v%s\n", appVersion)
|
||||
fmt.Printf("auspex v%s (%s)\n", appVersion, buildVariant)
|
||||
|
||||
case "-h", "--help":
|
||||
printUsage()
|
||||
|
|
@ -130,13 +136,14 @@ func showMenu() {
|
|||
fmt.Printf("%sВыберите утилиту:%s\n\n", cBlue, cReset)
|
||||
fmt.Printf(" %s1%s) %s-ss%s Просмотр занятых портов (TUI интерфейс)\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s2%s) %s-c %s Проверка SSL сертификатов\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s3%s) %s-p %s Проверка доступности портов (nginx upstream)\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s4%s) %s-m %s Проверка совпадений портов Angie ↔ Docker\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s5%s) %s-l %s Проверка активности логов (PostgreSQL)\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s3%s) %s-p %s Проверка доступности портов (nginx upstream) [WAF]\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s4%s) %s-m %s Проверка совпадений портов Angie ↔ Docker [WAF]\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s5%s) %s-l %s Проверка активности логов (PostgreSQL) [WAF]\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s6%s) %s-sv%s Проверка состояния служб (systemd)\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s7%s) %s-dc%s Проверка контейнеров Docker\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s8%s) %s-w %s Проверка доступности сайтов (lynx) [sid]\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s8%s) %s-w %s Проверка доступности сайтов (lynx) [sid] [WAF]\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s9%s) %s-fd%s Удалённые-но-открытые файловые дескрипторы\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s10%s) %s-dt%s Docker Live — TUI (аналог lazydocker)\n", cGreen, cReset, cCyan, cReset)
|
||||
fmt.Printf(" %s0%s) Выход\n\n", cGreen, cReset)
|
||||
fmt.Printf("%sДля быстрого вызова: auspex <ключ>%s\n", cYellow, cReset)
|
||||
fmt.Printf("%sПример: auspex -ss | auspex -c | auspex -p | auspex -m [клиент] | auspex -l | auspex -sv | auspex -dc | auspex -w <sid>%s\n\n", cYellow, cReset)
|
||||
|
|
@ -200,6 +207,11 @@ func showMenu() {
|
|||
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
|
||||
}
|
||||
return
|
||||
case "10", "-dt", "dt":
|
||||
if err := runDockerTUI(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Ошибка: %v\n", err)
|
||||
}
|
||||
return
|
||||
case "0", "q", "quit", "exit":
|
||||
fmt.Println("Выход.")
|
||||
return
|
||||
|
|
@ -218,7 +230,12 @@ func printBanner() {
|
|||
██║ ██║╚██████╔╝███████║██║ ███████╗██╔╝ ██╗
|
||||
╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚══════╝╚═╝ ╚═╝
|
||||
%s`, cCyan, cReset)
|
||||
fmt.Printf(" %sv%s%s — набор утилит мониторинга инфраструктуры\n\n", cBlue, appVersion, cReset)
|
||||
fmt.Printf(" %sv%s%s — набор утилит мониторинга инфраструктуры\n", cBlue, appVersion, cReset)
|
||||
if buildHasWAF {
|
||||
fmt.Printf(" %sСборка: full (включает WAF-модули -p, -l, -m, -w)%s\n\n", cYellow, cReset)
|
||||
} else {
|
||||
fmt.Printf(" %sСборка: generic (без WAF-модулей -p, -l, -m, -w — см. -h)%s\n\n", cYellow, cReset)
|
||||
}
|
||||
}
|
||||
|
||||
func printUsage() {
|
||||
|
|
@ -226,14 +243,20 @@ func printUsage() {
|
|||
fmt.Printf("Флаги:\n")
|
||||
fmt.Printf(" %-12s Просмотр занятых портов (интерактивный TUI)\n", "-ss")
|
||||
fmt.Printf(" %-12s Проверка SSL сертификатов в %s\n", "-c", cfgSSLDir)
|
||||
fmt.Printf(" %-12s Проверка доступности портов (nginx upstream)\n", "-p")
|
||||
fmt.Printf(" %-12s Проверка совпадений портов Angie ↔ Docker [имя_клиента]\n", "-m")
|
||||
fmt.Printf(" %-12s Проверка активности логов (PostgreSQL)\n", "-l")
|
||||
fmt.Printf(" %-12s Проверка доступности портов (nginx upstream) [WAF]\n", "-p")
|
||||
fmt.Printf(" %-12s Проверка совпадений портов Angie ↔ Docker [имя_клиента] [WAF]\n", "-m")
|
||||
fmt.Printf(" %-12s Проверка активности логов (PostgreSQL) [WAF]\n", "-l")
|
||||
fmt.Printf(" %-12s Проверка состояния служб systemd\n", "-sv")
|
||||
fmt.Printf(" %-12s Проверка контейнеров Docker\n", "-dc")
|
||||
fmt.Printf(" %-12s Проверка доступности сайтов через lynx [sid]\n", "-w")
|
||||
fmt.Printf(" %-12s Проверка доступности сайтов через lynx [sid] [WAF]\n", "-w")
|
||||
fmt.Printf(" %-12s Удалённые-но-открытые файловые дескрипторы (recover/reopen/restart/kill)\n", "-fd")
|
||||
fmt.Printf(" %-12s Docker Live — интерактивный TUI (аналог lazydocker)\n", "-dt")
|
||||
fmt.Printf(" %-12s Показать версию\n", "-v")
|
||||
fmt.Printf(" %-12s Показать эту справку\n\n", "-h")
|
||||
fmt.Printf("Без аргументов запускается интерактивное меню.\n")
|
||||
}
|
||||
if !buildHasWAF {
|
||||
fmt.Printf("\n%s⚠️ Эта сборка (generic) не включает модули [WAF] (-p, -m, -l, -w) —\n"+
|
||||
" они требуют БД waf_info / структуры PT AF. Соберите с 'go build -tags waf .',\n"+
|
||||
" чтобы получить полную (full) сборку.%s\n", cYellow, cReset)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
2
matchcheck.go
Executable file → Normal file
2
matchcheck.go
Executable file → Normal file
|
|
@ -1,3 +1,5 @@
|
|||
//go:build waf
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
|||
14
matchcheck_stub.go
Normal file
14
matchcheck_stub.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
//go:build !waf
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Заглушка для сборки без тега 'waf'. Реальная реализация — в matchcheck.go
|
||||
// (собирается только с 'go build -tags waf .'). БД не использует, но жёстко
|
||||
// завязан на структуру каталогов и нейминг контейнеров PT AF (cfgPTAFBaseDir,
|
||||
// паттерны вида "{client}-ptaf-agent003"), поэтому на generic-сервере бесполезен.
|
||||
func runMatchCheck(clientFilter string) error {
|
||||
return fmt.Errorf("модуль -m (matchcheck) недоступен в этой сборке: завязан на структуру PT AF.\n" +
|
||||
"Пересоберите бинарник с 'go build -tags waf .', чтобы получить этот модуль")
|
||||
}
|
||||
2
portcheck.go
Executable file → Normal file
2
portcheck.go
Executable file → Normal file
|
|
@ -1,3 +1,5 @@
|
|||
//go:build waf
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
|||
13
portcheck_stub.go
Normal file
13
portcheck_stub.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//go:build !waf
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Заглушка для сборки без тега 'waf'. Реальная реализация — в portcheck.go
|
||||
// (собирается только с 'go build -tags waf .'), т.к. модуль фильтрует конфиги
|
||||
// через флаг check_ports из таблицы apps_settings в БД waf_info.
|
||||
func runPortCheck() error {
|
||||
return fmt.Errorf("модуль -p (portcheck) недоступен в этой сборке: требует БД waf_info.\n" +
|
||||
"Пересоберите бинарник с 'go build -tags waf .', чтобы получить этот модуль")
|
||||
}
|
||||
2
webcheck.go
Executable file → Normal file
2
webcheck.go
Executable file → Normal file
|
|
@ -1,3 +1,5 @@
|
|||
//go:build waf
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
|
|
|
|||
13
webcheck_stub.go
Normal file
13
webcheck_stub.go
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
//go:build !waf
|
||||
|
||||
package main
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Заглушка для сборки без тега 'waf'. Реальная реализация — в webcheck.go
|
||||
// (собирается только с 'go build -tags waf .'), т.к. модуль читает домены
|
||||
// клиента из таблицы sp_info в БД waf_info.
|
||||
func runWebCheck(sidArg string) error {
|
||||
return fmt.Errorf("модуль -w (webcheck) недоступен в этой сборке: требует БД waf_info.\n" +
|
||||
"Пересоберите бинарник с 'go build -tags waf .', чтобы получить этот модуль")
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue