New system package checks `apt list --upgradable` against the current package index (no apt update triggered) and the reboot-required marker file. Read-only - never installs or upgrades anything. Shown as a card on the About page with a manual "Check now" refresh. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
64 lines
1.9 KiB
Go
64 lines
1.9 KiB
Go
// Package system checks the host OS for pending package updates. It never
|
|
// installs or upgrades anything - read-only status only, using the
|
|
// existing apt package index (no "apt update" is run automatically, since
|
|
// that touches network/package state on every page load).
|
|
package system
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"os"
|
|
"os/exec"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// UpdateStatus summarizes pending OS package updates on a Debian/Ubuntu host.
|
|
type UpdateStatus struct {
|
|
Supported bool `json:"supported"` // false if apt isn't available (e.g. non-Debian host)
|
|
UpgradableCount int `json:"upgradable_count"`
|
|
Packages []string `json:"packages"`
|
|
RebootRequired bool `json:"reboot_required"`
|
|
CheckedAt time.Time `json:"checked_at"`
|
|
Error string `json:"error,omitempty"`
|
|
}
|
|
|
|
// CheckAptUpdates lists upgradable packages from the current apt index
|
|
// (`apt list --upgradable`) without refreshing it, and checks for the
|
|
// standard Debian/Ubuntu reboot-required marker file.
|
|
func CheckAptUpdates() UpdateStatus {
|
|
status := UpdateStatus{CheckedAt: time.Now().UTC()}
|
|
|
|
if _, err := exec.LookPath("apt"); err != nil {
|
|
status.Supported = false
|
|
return status
|
|
}
|
|
status.Supported = true
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
|
defer cancel()
|
|
|
|
cmd := exec.CommandContext(ctx, "apt", "list", "--upgradable")
|
|
out, err := cmd.Output()
|
|
if err != nil {
|
|
status.Error = "cannot list upgradable packages: " + err.Error()
|
|
return status
|
|
}
|
|
|
|
scanner := bufio.NewScanner(strings.NewReader(string(out)))
|
|
for scanner.Scan() {
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if line == "" || strings.HasPrefix(line, "Listing...") {
|
|
continue
|
|
}
|
|
status.Packages = append(status.Packages, line)
|
|
}
|
|
status.UpgradableCount = len(status.Packages)
|
|
|
|
if _, err := os.Stat("/var/run/reboot-required"); err == nil {
|
|
status.RebootRequired = true
|
|
}
|
|
|
|
return status
|
|
}
|