fix: improve feature completeness and correctness

Pass 1 (Feature & Completeness):
- Replace apt CLI with apt-get for listUpgrades (apt CLI is unstable for scripting)
- Verify snapd daemon is running in snap Available() check
- Add ErrDaemonNotRunning sentinel error for daemon-dependent managers
- Fix staticcheck S1011: replace loop with append(keys, matches...)
- Fix staticcheck SA1012: use context.TODO() instead of nil in dpkg tests
This commit is contained in:
2026-02-26 01:26:51 +00:00
parent d4c7a058fb
commit 6edb79df3f
6 changed files with 45 additions and 32 deletions

View File

@@ -37,37 +37,43 @@ func latestVersion(ctx context.Context, pkg string) (string, error) {
}
func listUpgrades(ctx context.Context) ([]snack.Package, error) {
cmd := exec.CommandContext(ctx, "apt", "list", "--upgradable")
cmd.Env = append(os.Environ(), "LANG=C")
// Use apt-get --just-print upgrade instead of `apt list --upgradable`
// because `apt` has unstable CLI output not intended for scripting.
cmd := exec.CommandContext(ctx, "apt-get", "--just-print", "upgrade")
cmd.Env = append(os.Environ(), "LANG=C", "DEBIAN_FRONTEND=noninteractive")
out, err := cmd.Output()
if err != nil {
return nil, fmt.Errorf("apt list --upgradable: %w", err)
return nil, fmt.Errorf("apt-get --just-print upgrade: %w", err)
}
var pkgs []snack.Package
for _, line := range strings.Split(string(out), "\n") {
line = strings.TrimSpace(line)
if line == "" || strings.HasPrefix(line, "Listing...") {
// Lines starting with "Inst " indicate upgradable packages.
// Format: "Inst pkg [old-ver] (new-ver repo [arch])"
if !strings.HasPrefix(line, "Inst ") {
continue
}
// Format: "pkg/source version arch [upgradable from: old-version]"
slashIdx := strings.Index(line, "/")
if slashIdx < 0 {
continue
}
name := line[:slashIdx]
rest := line[slashIdx+1:]
fields := strings.Fields(rest)
line = strings.TrimPrefix(line, "Inst ")
fields := strings.Fields(line)
if len(fields) < 2 {
continue
}
name := fields[0]
// Find the new version in parentheses
parenStart := strings.Index(line, "(")
parenEnd := strings.Index(line, ")")
if parenStart < 0 || parenEnd < 0 {
continue
}
verFields := strings.Fields(line[parenStart+1 : parenEnd])
if len(verFields) < 1 {
continue
}
p := snack.Package{
Name: name,
Version: fields[1],
Version: verFields[0],
Installed: true,
}
if len(fields) > 2 {
p.Arch = fields[2]
}
pkgs = append(pkgs, p)
}
return pkgs, nil
@@ -361,9 +367,7 @@ func listKeys(ctx context.Context) ([]string, error) {
// List keyring files
matches, _ := filepath.Glob("/etc/apt/keyrings/*.gpg")
for _, m := range matches {
keys = append(keys, m)
}
keys = append(keys, matches...)
ascMatches, _ := filepath.Glob("/etc/apt/keyrings/*.asc")
keys = append(keys, ascMatches...)