|
|
|
|
@@ -8,7 +8,6 @@ import (
|
|
|
|
|
"io"
|
|
|
|
|
"net"
|
|
|
|
|
"net/http"
|
|
|
|
|
"os"
|
|
|
|
|
"os/exec"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"regexp"
|
|
|
|
|
@@ -72,7 +71,7 @@ func (s *Store) QueueAgentAction(agentID, action, target string) (AgentAction, e
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case "updates-refresh", "system-update":
|
|
|
|
|
case "updates-refresh", "system-update", "system-reboot":
|
|
|
|
|
valid = target == ""
|
|
|
|
|
}
|
|
|
|
|
if !valid {
|
|
|
|
|
@@ -172,6 +171,8 @@ func executeAgentCommand(command AgentCommand) AgentActionResult {
|
|
|
|
|
output = removeAgentContainer(command.Target)
|
|
|
|
|
case "system-update":
|
|
|
|
|
output = updateAgentSystem()
|
|
|
|
|
case "system-reboot":
|
|
|
|
|
output = scheduleAgentReboot()
|
|
|
|
|
case "updates-refresh":
|
|
|
|
|
guestInventory.Lock()
|
|
|
|
|
guestInventory.value.CollectedAt = time.Time{}
|
|
|
|
|
@@ -210,25 +211,59 @@ func removeAgentContainer(target string) string {
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateAgentSystem() string {
|
|
|
|
|
if output := runAgentCombinedCommand(3*time.Minute, "apt-get", "update"); strings.HasPrefix(output, "ERROR: ") {
|
|
|
|
|
return output
|
|
|
|
|
}
|
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Minute)
|
|
|
|
|
defer cancel()
|
|
|
|
|
command := exec.CommandContext(ctx, "apt-get", "-y", "upgrade")
|
|
|
|
|
command.Env = append(os.Environ(), "DEBIAN_FRONTEND=noninteractive")
|
|
|
|
|
output, err := command.CombinedOutput()
|
|
|
|
|
message := strings.TrimSpace(string(output))
|
|
|
|
|
if len(message) > 3500 {
|
|
|
|
|
message = message[len(message)-3500:]
|
|
|
|
|
}
|
|
|
|
|
aptGet, err := exec.LookPath("apt-get")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "ERROR: " + err.Error() + ": " + message
|
|
|
|
|
return "ERROR: apt-get не найден; обновление поддерживается для Debian/Ubuntu"
|
|
|
|
|
}
|
|
|
|
|
if output := runPrivilegedTransient(5*time.Minute, "update-lists", aptGet, "update"); strings.HasPrefix(output, "ERROR: ") {
|
|
|
|
|
return explainSystemUpdateError(output)
|
|
|
|
|
}
|
|
|
|
|
message := runPrivilegedTransient(30*time.Minute, "upgrade", aptGet, "-y", "upgrade")
|
|
|
|
|
if strings.HasPrefix(message, "ERROR: ") {
|
|
|
|
|
return explainSystemUpdateError(message)
|
|
|
|
|
}
|
|
|
|
|
guestInventory.Lock()
|
|
|
|
|
guestInventory.value.CollectedAt = time.Time{}
|
|
|
|
|
guestInventory.Unlock()
|
|
|
|
|
return firstNonEmpty(message, "Обновления системы установлены")
|
|
|
|
|
return "Обновления системы установлены; список пакетов будет обновлён в следующем отчёте агента"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func explainSystemUpdateError(message string) string {
|
|
|
|
|
if strings.Contains(strings.ToLower(message), "read-only file system") {
|
|
|
|
|
return "ERROR: файловая система агента доступна только для чтения. Обновите Dashboard и агент до v0.19.2 или новее, затем повторите операцию."
|
|
|
|
|
}
|
|
|
|
|
if len(message) > 1800 {
|
|
|
|
|
message = message[len(message)-1800:]
|
|
|
|
|
}
|
|
|
|
|
return message
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runPrivilegedTransient(timeout time.Duration, suffix, name string, args ...string) string {
|
|
|
|
|
systemdRun, err := exec.LookPath("systemd-run")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "ERROR: systemd-run не найден; невозможно безопасно выйти из read-only sandbox агента"
|
|
|
|
|
}
|
|
|
|
|
unit := fmt.Sprintf("proxmox-dashboard-%s-%d", suffix, time.Now().UnixNano())
|
|
|
|
|
commandArgs := []string{"--quiet", "--wait", "--collect", "--pipe", "--unit=" + unit, "--setenv=DEBIAN_FRONTEND=noninteractive", name}
|
|
|
|
|
commandArgs = append(commandArgs, args...)
|
|
|
|
|
return runAgentCombinedCommand(timeout, systemdRun, commandArgs...)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func scheduleAgentReboot() string {
|
|
|
|
|
systemdRun, err := exec.LookPath("systemd-run")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "ERROR: systemd-run не найден"
|
|
|
|
|
}
|
|
|
|
|
systemctl, err := exec.LookPath("systemctl")
|
|
|
|
|
if err != nil {
|
|
|
|
|
return "ERROR: systemctl не найден"
|
|
|
|
|
}
|
|
|
|
|
unit := fmt.Sprintf("proxmox-dashboard-reboot-%d", time.Now().UnixNano())
|
|
|
|
|
output := runAgentCombinedCommand(15*time.Second, systemdRun, "--quiet", "--collect", "--unit="+unit, "--on-active=20s", systemctl, "reboot")
|
|
|
|
|
if strings.HasPrefix(output, "ERROR: ") {
|
|
|
|
|
return output
|
|
|
|
|
}
|
|
|
|
|
return "Перезагрузка запланирована через 20 секунд; кратковременный offline агента ожидаем"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func updateAgentContainer(target string, recreate bool) string {
|
|
|
|
|
|