feat: add Arch update helper and web UI

This commit is contained in:
2026-05-03 22:48:01 +02:00
parent cbe105a719
commit 653ff6d298
12 changed files with 1349 additions and 0 deletions

149
internal/updater/updater.go Normal file
View File

@@ -0,0 +1,149 @@
package updater
import (
"bufio"
"context"
"os/exec"
"strings"
)
type Package struct {
Source string
Name string
Current string
Available string
}
type Result struct {
Packages []Package
Warnings []string
}
type Options struct {
CheckAUR bool
}
func (r Result) Total() int {
return len(r.Packages)
}
func (r Result) Summary() string {
total := r.Total()
switch total {
case 0:
return "No updates available."
case 1:
return "1 update available."
default:
return strings.TrimSpace(strings.Join([]string{itoa(total), "updates available."}, " "))
}
}
func Check(ctx context.Context) (Result, error) {
return CheckWithOptions(ctx, Options{CheckAUR: true})
}
func CheckWithOptions(ctx context.Context, opts Options) (Result, error) {
var result Result
pacman, err := checkPacman(ctx)
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
}
result.Packages = append(result.Packages, pacman...)
if opts.CheckAUR {
aur, err := checkAUR(ctx)
if err != nil {
result.Warnings = append(result.Warnings, err.Error())
}
result.Packages = append(result.Packages, aur...)
}
return result, nil
}
func checkPacman(ctx context.Context) ([]Package, error) {
if _, err := exec.LookPath("checkupdates"); err == nil {
out, err := exec.CommandContext(ctx, "checkupdates").Output()
if err != nil {
return nil, nil
}
return parseUpdates("pacman", string(out)), nil
}
out, err := exec.CommandContext(ctx, "pacman", "-Qu").Output()
if err != nil {
return nil, nil
}
return parseUpdates("pacman", string(out)), nil
}
func checkAUR(ctx context.Context) ([]Package, error) {
helper := AURHelper()
if helper == "" {
return nil, nil
}
out, err := exec.CommandContext(ctx, helper, "-Qua").Output()
if err != nil {
return nil, nil
}
return parseUpdates("aur", string(out)), nil
}
func AURHelper() string {
for _, name := range []string{"paru", "yay"} {
if _, err := exec.LookPath(name); err == nil {
return name
}
}
return ""
}
func parseUpdates(source, output string) []Package {
var packages []Package
scanner := bufio.NewScanner(strings.NewReader(output))
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "" {
continue
}
packages = append(packages, parseLine(source, line))
}
return packages
}
func parseLine(source, line string) Package {
fields := strings.Fields(line)
pkg := Package{Source: source}
if len(fields) == 0 {
return pkg
}
pkg.Name = fields[0]
if len(fields) >= 4 && fields[2] == "->" {
pkg.Current = fields[1]
pkg.Available = fields[3]
return pkg
}
if len(fields) >= 3 && fields[1] == "->" {
pkg.Available = fields[2]
return pkg
}
return pkg
}
func itoa(n int) string {
if n == 0 {
return "0"
}
var buf [20]byte
i := len(buf)
for n > 0 {
i--
buf[i] = byte('0' + n%10)
n /= 10
}
return string(buf[i:])
}