Files
wireguard-ui-multi/system/updates.go
T
sysopsandClaude Sonnet 5 bdcf1ec60c Add read-only OS package update status (apt) on the About page
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>
2026-07-12 17:35:27 +02:00

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
}