Add backup maintenance and guest update controls

This commit is contained in:
Maxim
2026-08-21 21:15:52 +03:00
parent 714c29c449
commit f6498a2fc1
10 changed files with 268 additions and 32 deletions

View File

@@ -3,6 +3,7 @@ package main
import (
"bufio"
"context"
"fmt"
"io"
"net"
"net/http"
@@ -36,6 +37,9 @@ type NetworkHealth struct {
ExternalIPChanged bool `json:"externalIpChanged"`
DisappearedInterfaces []string `json:"disappearedInterfaces"`
CheckedAt time.Time `json:"checkedAt"`
Summary string `json:"summary"`
LikelyCause string `json:"likelyCause"`
RecommendedAction string `json:"recommendedAction"`
}
type NetworkInterface struct {
@@ -46,6 +50,8 @@ type NetworkInterface struct {
TransmitErrors uint64 `json:"transmitErrors"`
ReceiveDrops uint64 `json:"receiveDrops"`
TransmitDrops uint64 `json:"transmitDrops"`
NewErrors uint64 `json:"newErrors"`
NewDrops uint64 `json:"newDrops"`
ReceivePerSec float64 `json:"receivePerSec"`
TransmitPerSec float64 `json:"transmitPerSec"`
VMID int `json:"vmid,omitempty"`
@@ -113,6 +119,12 @@ func (c *networkCollector) collect() (NetworkMetrics, error) {
if old, ok := c.previousInterfaces[v.Name]; ok && elapsed > 0 && v.ReceiveBytes >= old.ReceiveBytes && v.TransmitBytes >= old.TransmitBytes {
v.ReceivePerSec = float64(v.ReceiveBytes-old.ReceiveBytes) / elapsed
v.TransmitPerSec = float64(v.TransmitBytes-old.TransmitBytes) / elapsed
if v.ReceiveErrors >= old.ReceiveErrors && v.TransmitErrors >= old.TransmitErrors {
v.NewErrors = v.ReceiveErrors - old.ReceiveErrors + v.TransmitErrors - old.TransmitErrors
}
if v.ReceiveDrops >= old.ReceiveDrops && v.TransmitDrops >= old.TransmitDrops {
v.NewDrops = v.ReceiveDrops - old.ReceiveDrops + v.TransmitDrops - old.TransmitDrops
}
}
}
c.previousInterfaces = map[string]NetworkInterface{}
@@ -138,12 +150,41 @@ func (c *networkCollector) collect() (NetworkMetrics, error) {
}
}
c.knownInterfaces = currentNames
explainNetworkHealth(&c.health, details)
return NetworkMetrics{
Interface: strings.Join(interfaceNames, ", "), Interfaces: details, ReceiveBytes: current.received,
TransmitBytes: current.sent, ReceivePerSec: receivedRate, TransmitPerSec: sentRate, Health: c.health,
}, nil
}
func explainNetworkHealth(health *NetworkHealth, interfaces []NetworkInterface) {
health.Summary, health.LikelyCause, health.RecommendedAction = "Сеть работает штатно", "Проблем не обнаружено", "Действий не требуется"
switch {
case health.Gateway == "":
health.Summary, health.LikelyCause, health.RecommendedAction = "Не найден маршрут в локальную сеть", "На хосте отсутствует default gateway", "Проверьте адрес шлюза и конфигурацию bridge/VLAN"
case !health.GatewayUp:
health.Summary, health.LikelyCause, health.RecommendedAction = "Хост не видит локальный шлюз", "Проблема между Proxmox и роутером: линк, bridge, bond или VLAN", "Проверьте кабель, carrier и настройки vmbr/bond/VLAN"
case health.PacketLoss > 0:
health.Summary = fmt.Sprintf("Нестабильная локальная сеть: потеряно %.1f%% пакетов", health.PacketLoss)
health.LikelyCause, health.RecommendedAction = "Перегрузка или ошибки линка между Proxmox и шлюзом", "Проверьте errors/drops, кабель, порт коммутатора и duplex"
case !health.DNSUp && health.InternetUp:
health.Summary, health.LikelyCause, health.RecommendedAction = "Интернет доступен, но DNS не отвечает", "Проблема DNS-сервера или /etc/resolv.conf", "Проверьте настроенные DNS и попробуйте разрешить домен вручную"
case health.DNSUp && !health.InternetUp:
health.Summary, health.LikelyCause, health.RecommendedAction = "Локальная сеть и DNS работают, но внешняя проверка недоступна", "Проблема выше роутера, блокировка HTTPS или недоступность провайдера", "Проверьте маршрут наружу, firewall и доступ к внешним адресам"
}
for _, v := range interfaces {
if v.Kind == "Physical" && (!v.Carrier || v.State == "down") {
health.Summary, health.LikelyCause, health.RecommendedAction = v.Name+": физический линк не поднят", "Нет carrier либо интерфейс административно выключен", "Проверьте кабель, порт коммутатора и состояние интерфейса"
return
}
if v.NewErrors+v.NewDrops > 0 {
health.Summary = fmt.Sprintf("%s: за последний интервал появилось %d сетевых ошибок/потерь", v.Name, v.NewErrors+v.NewDrops)
health.LikelyCause, health.RecommendedAction = "Счётчики интерфейса сейчас растут из-за перегрузки, кабеля, MTU или duplex", "Проверьте скорость и duplex, кабель, порт коммутатора и MTU на обоих концах"
return
}
}
}
func decorateInterface(v *NetworkInterface) {
base := filepath.Join("/sys/class/net", v.Name)
v.State = readTrimmed(filepath.Join(base, "operstate"))