mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-05-06 04:22:11 -04:00
refactor(sysupdate): Streamline DMS Updater command handling
This commit is contained in:
@@ -100,29 +100,13 @@ func runSystemUpdateCheck() {
|
||||
}
|
||||
|
||||
stopSpin := startSpinner("Checking for updates… ")
|
||||
|
||||
type backendResult struct {
|
||||
ID string `json:"id"`
|
||||
Display string `json:"displayName"`
|
||||
Packages []sysupdate.Package `json:"packages"`
|
||||
}
|
||||
var results []backendResult
|
||||
var allPkgs []sysupdate.Package
|
||||
var firstErr error
|
||||
|
||||
for _, b := range backends {
|
||||
pkgs, err := b.CheckUpdates(ctx)
|
||||
if err != nil && firstErr == nil {
|
||||
firstErr = fmt.Errorf("%s: %w", b.ID(), err)
|
||||
}
|
||||
results = append(results, backendResult{ID: b.ID(), Display: b.DisplayName(), Packages: pkgs})
|
||||
allPkgs = append(allPkgs, pkgs...)
|
||||
}
|
||||
allPkgs, firstErr := collectUpdates(ctx, backends)
|
||||
stopSpin()
|
||||
allPkgs = filterUpdateTargets(allPkgs)
|
||||
|
||||
if sysUpdateJSON {
|
||||
out, _ := json.MarshalIndent(map[string]any{
|
||||
"backends": results,
|
||||
"backends": backendResults(backends, allPkgs),
|
||||
"packages": allPkgs,
|
||||
"error": errOrEmpty(firstErr),
|
||||
"count": len(allPkgs),
|
||||
@@ -145,6 +129,26 @@ func runSystemUpdateCheck() {
|
||||
}
|
||||
}
|
||||
|
||||
type backendResult struct {
|
||||
ID string `json:"id"`
|
||||
Display string `json:"displayName"`
|
||||
Packages []sysupdate.Package `json:"packages"`
|
||||
}
|
||||
|
||||
func backendResults(backends []sysupdate.Backend, pkgs []sysupdate.Package) []backendResult {
|
||||
results := make([]backendResult, 0, len(backends))
|
||||
for _, b := range backends {
|
||||
var backendPkgs []sysupdate.Package
|
||||
for _, p := range pkgs {
|
||||
if sysupdate.BackendHasTargets(b, []sysupdate.Package{p}, true, true) {
|
||||
backendPkgs = append(backendPkgs, p)
|
||||
}
|
||||
}
|
||||
results = append(results, backendResult{ID: b.ID(), Display: b.DisplayName(), Packages: backendPkgs})
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
func runSystemUpdateApply() {
|
||||
checkCtx, checkCancel := context.WithTimeout(context.Background(), sysUpdateListPmTime)
|
||||
defer checkCancel()
|
||||
@@ -157,6 +161,7 @@ func runSystemUpdateApply() {
|
||||
stopSpin := startSpinner("Checking for updates…")
|
||||
pkgs, firstErr := collectUpdates(checkCtx, backends)
|
||||
stopSpin()
|
||||
pkgs = filterUpdateTargets(pkgs)
|
||||
if firstErr != nil {
|
||||
fmt.Printf("Warning: %v\n\n", firstErr)
|
||||
}
|
||||
@@ -190,14 +195,24 @@ func runSystemUpdateApply() {
|
||||
DryRun: sysUpdateDry,
|
||||
UseSudo: true,
|
||||
}
|
||||
opts.AttachStdio = sysupdate.UpgradeNeedsPrivilege(backends, pkgs, opts)
|
||||
|
||||
onLine := func(line string) { fmt.Println(line) }
|
||||
ran := false
|
||||
for _, b := range backends {
|
||||
if !sysupdate.BackendHasTargets(b, pkgs, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
continue
|
||||
}
|
||||
ran = true
|
||||
fmt.Printf("\n== %s ==\n", b.DisplayName())
|
||||
if err := b.Upgrade(ctx, opts, onLine); err != nil {
|
||||
log.Fatalf("%s upgrade failed: %v", b.ID(), err)
|
||||
}
|
||||
}
|
||||
if !ran {
|
||||
fmt.Println("Nothing to upgrade.")
|
||||
return
|
||||
}
|
||||
if sysUpdateDry {
|
||||
fmt.Println("\nDry run complete (no changes applied).")
|
||||
return
|
||||
@@ -218,6 +233,20 @@ func collectUpdates(ctx context.Context, backends []sysupdate.Backend) ([]sysupd
|
||||
return all, firstErr
|
||||
}
|
||||
|
||||
func filterUpdateTargets(pkgs []sysupdate.Package) []sysupdate.Package {
|
||||
if !sysUpdateNoAUR {
|
||||
return pkgs
|
||||
}
|
||||
out := pkgs[:0]
|
||||
for _, p := range pkgs {
|
||||
if p.Repo == sysupdate.RepoAUR {
|
||||
continue
|
||||
}
|
||||
out = append(out, p)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func runSystemUpdateSetInterval(seconds int) {
|
||||
resp, err := sendServerRequest(models.Request{
|
||||
ID: 1,
|
||||
|
||||
@@ -45,13 +45,14 @@ func (aptBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(
|
||||
OnLine: onLine,
|
||||
})
|
||||
}
|
||||
names := pickTargetNames(opts.Targets, "apt", true)
|
||||
if len(names) == 0 {
|
||||
if !BackendHasTargets(aptBackend{}, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
privesc := privescBin(opts.UseSudo)
|
||||
argv := append([]string{privesc, "env", "DEBIAN_FRONTEND=noninteractive", "LC_ALL=C", bin, "install", "-y", "--only-upgrade"}, names...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
return Run(ctx, aptUpgradeArgv(bin, opts), RunOptions{OnLine: onLine, AttachStdio: opts.AttachStdio})
|
||||
}
|
||||
|
||||
func aptUpgradeArgv(bin string, opts UpgradeOptions) []string {
|
||||
return privilegedArgv(opts, "env", "DEBIAN_FRONTEND=noninteractive", "LC_ALL=C", bin, "upgrade", "-y")
|
||||
}
|
||||
|
||||
func parseAptUpgradable(text string) []Package {
|
||||
|
||||
@@ -45,27 +45,37 @@ func (b dnfBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine fun
|
||||
if opts.DryRun {
|
||||
return Run(ctx, []string{b.bin, "upgrade", "--assumeno"}, RunOptions{OnLine: onLine})
|
||||
}
|
||||
names := pickTargetNames(opts.Targets, b.bin, true)
|
||||
if len(names) == 0 {
|
||||
if !BackendHasTargets(b, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
privesc := privescBin(opts.UseSudo)
|
||||
argv := append([]string{privesc, b.bin, "upgrade", "-y"}, names...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
return Run(ctx, dnfUpgradeArgv(b.bin, opts), RunOptions{OnLine: onLine, AttachStdio: opts.AttachStdio})
|
||||
}
|
||||
|
||||
func dnfUpgradeArgv(bin string, opts UpgradeOptions) []string {
|
||||
return privilegedArgv(opts, bin, "upgrade", "--refresh", "-y")
|
||||
}
|
||||
|
||||
func dnfListUpgrades(ctx context.Context, bin string) (string, error) {
|
||||
cmd := exec.CommandContext(ctx, bin, "list", "--upgrades", "--refresh", "--quiet")
|
||||
out, err := cmd.Output()
|
||||
argv := dnfCheckUpdatesArgv(bin)
|
||||
cmd := exec.CommandContext(ctx, argv[0], argv[1:]...)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err == nil {
|
||||
return string(out), nil
|
||||
}
|
||||
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok && exitErr.ExitCode() == 1 {
|
||||
return "", nil
|
||||
if exitErr, ok := errors.AsType[*exec.ExitError](err); ok && exitErr.ExitCode() == 100 {
|
||||
return string(out), nil
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func dnfCheckUpdatesArgv(bin string) []string {
|
||||
subcommand := "check-update"
|
||||
if bin == "dnf5" {
|
||||
subcommand = "check-upgrade"
|
||||
}
|
||||
return []string{bin, subcommand, "--refresh", "--quiet"}
|
||||
}
|
||||
|
||||
func rpmInstalledVersions(ctx context.Context) map[string]string {
|
||||
out, err := exec.CommandContext(ctx, "rpm", "-qa", "--qf", `%{NAME}\t%{VERSION}-%{RELEASE}\n`).Output()
|
||||
if err != nil {
|
||||
|
||||
@@ -67,6 +67,21 @@ bash.x86_64 5.2.40-1.fc41 updates`,
|
||||
{Name: "bash.x86_64", Repo: RepoSystem, Backend: "dnf", FromVersion: "", ToVersion: "5.2.40-1.fc41"},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "skips dnf warning lines while keeping package rows",
|
||||
input: `Failed to expire repository cache in path "/home/user/.cache/libdnf5/updates": cannot open file
|
||||
example-driver.x86_64 2:9.8.7-1.fc99 updates
|
||||
example-tool.noarch 1.2.3^45.gitabcdef-1.fc99 copr`,
|
||||
backendID: "dnf5",
|
||||
installed: map[string]string{
|
||||
"example-driver": "2:9.8.6-1.fc99",
|
||||
"example-tool": "1.2.2^44.gitabcdef-1.fc99",
|
||||
},
|
||||
want: []Package{
|
||||
{Name: "example-driver.x86_64", Repo: RepoSystem, Backend: "dnf5", FromVersion: "2:9.8.6-1.fc99", ToVersion: "2:9.8.7-1.fc99"},
|
||||
{Name: "example-tool.noarch", Repo: RepoSystem, Backend: "dnf5", FromVersion: "1.2.2^44.gitabcdef-1.fc99", ToVersion: "1.2.3^45.gitabcdef-1.fc99"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
@@ -78,3 +93,22 @@ bash.x86_64 5.2.40-1.fc41 updates`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDnfCheckUpdatesArgv(t *testing.T) {
|
||||
tests := []struct {
|
||||
bin string
|
||||
want []string
|
||||
}{
|
||||
{bin: "dnf5", want: []string{"dnf5", "check-upgrade", "--refresh", "--quiet"}},
|
||||
{bin: "dnf", want: []string{"dnf", "check-update", "--refresh", "--quiet"}},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.bin, func(t *testing.T) {
|
||||
got := dnfCheckUpdatesArgv(tt.bin)
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Fatalf("dnfCheckUpdatesArgv(%q) = %#v, want %#v", tt.bin, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -73,27 +73,14 @@ func (flatpakBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine f
|
||||
if opts.DryRun {
|
||||
return Run(ctx, []string{"flatpak", "update", "--no-deploy", "-y"}, RunOptions{OnLine: onLine})
|
||||
}
|
||||
refs := flatpakTargetRefs(opts.Targets)
|
||||
if len(refs) == 0 {
|
||||
if !BackendHasTargets(flatpakBackend{}, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
argv := append([]string{"flatpak", "update", "-y", "--noninteractive"}, refs...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
return Run(ctx, flatpakUpgradeArgv(), RunOptions{OnLine: onLine})
|
||||
}
|
||||
|
||||
func flatpakTargetRefs(targets []Package) []string {
|
||||
out := make([]string, 0, len(targets))
|
||||
for _, p := range targets {
|
||||
if p.Backend != "flatpak" {
|
||||
continue
|
||||
}
|
||||
ref := p.Ref
|
||||
if ref == "" {
|
||||
ref = p.Name
|
||||
}
|
||||
out = append(out, ref)
|
||||
}
|
||||
return out
|
||||
func flatpakUpgradeArgv() []string {
|
||||
return []string{"flatpak", "update", "-y", "--noninteractive"}
|
||||
}
|
||||
|
||||
func parseFlatpakUpdates(text string, installed map[string]flatpakInstalledEntry) []Package {
|
||||
|
||||
@@ -43,13 +43,14 @@ func (b pacmanBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine
|
||||
if opts.DryRun {
|
||||
return Run(ctx, []string{"pacman", "-Sup"}, RunOptions{OnLine: onLine})
|
||||
}
|
||||
names := pickTargetNames(opts.Targets, b.ID(), opts.IncludeAUR)
|
||||
if len(names) == 0 {
|
||||
if !BackendHasTargets(b, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
privesc := privescBin(opts.UseSudo)
|
||||
argv := append([]string{privesc, "pacman", "-Sy", "--noconfirm", "--needed"}, names...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
return Run(ctx, pacmanUpgradeArgv(opts), RunOptions{OnLine: onLine, AttachStdio: opts.AttachStdio})
|
||||
}
|
||||
|
||||
func pacmanUpgradeArgv(opts UpgradeOptions) []string {
|
||||
return privilegedArgv(opts, "pacman", "-Syu", "--noconfirm", "--needed")
|
||||
}
|
||||
|
||||
type archHelperBackend struct {
|
||||
@@ -94,35 +95,28 @@ func (b archHelperBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onL
|
||||
if opts.DryRun {
|
||||
return Run(ctx, []string{b.id, "-Sup"}, RunOptions{OnLine: onLine})
|
||||
}
|
||||
names := pickTargetNames(opts.Targets, b.id, opts.IncludeAUR)
|
||||
if len(names) == 0 {
|
||||
if !BackendHasTargets(b, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
if os.Getenv("DMS_FORCE_PKEXEC") == "1" {
|
||||
argv := append([]string{"pkexec", b.id, "-Sy", "--noconfirm", "--needed"}, names...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
argv := append([]string{"pkexec"}, archHelperUpgradeArgv(b.id, opts.IncludeAUR)...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine, AttachStdio: opts.AttachStdio})
|
||||
}
|
||||
term := findTerminal(opts.Terminal)
|
||||
if term == "" {
|
||||
return fmt.Errorf("no terminal found (pick one in DMS settings, set $TERMINAL, or install kitty/ghostty/foot/alacritty)")
|
||||
}
|
||||
cmd := fmt.Sprintf("%s -Sy --noconfirm --needed %s", b.id, strings.Join(names, " "))
|
||||
cmd := strings.Join(archHelperUpgradeArgv(b.id, opts.IncludeAUR), " ")
|
||||
title := fmt.Sprintf("DMS — System Update (%s)", b.id)
|
||||
return Run(ctx, wrapInTerminal(term, title, cmd), RunOptions{OnLine: onLine})
|
||||
}
|
||||
|
||||
func pickTargetNames(targets []Package, backendID string, includeAUR bool) []string {
|
||||
out := make([]string, 0, len(targets))
|
||||
for _, p := range targets {
|
||||
if p.Backend != backendID {
|
||||
continue
|
||||
}
|
||||
if !includeAUR && p.Repo == RepoAUR {
|
||||
continue
|
||||
}
|
||||
out = append(out, p.Name)
|
||||
func archHelperUpgradeArgv(id string, includeAUR bool) []string {
|
||||
argv := []string{id, "-Syu", "--noconfirm", "--needed"}
|
||||
if !includeAUR {
|
||||
argv = append(argv, "--repo")
|
||||
}
|
||||
return out
|
||||
return argv
|
||||
}
|
||||
|
||||
func pacmanRepoUpdates(ctx context.Context) (string, error) {
|
||||
|
||||
@@ -117,9 +117,16 @@ func bootedDeployment(deps []ostreeDeployment) *ostreeDeployment {
|
||||
}
|
||||
|
||||
func (rpmOstreeBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
|
||||
if !BackendHasTargets(rpmOstreeBackend{}, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
return Run(ctx, rpmOstreeUpgradeArgv(opts), RunOptions{OnLine: onLine, AttachStdio: opts.AttachStdio})
|
||||
}
|
||||
|
||||
func rpmOstreeUpgradeArgv(opts UpgradeOptions) []string {
|
||||
argv := []string{"rpm-ostree", "upgrade"}
|
||||
if opts.DryRun {
|
||||
argv = append(argv, "--check")
|
||||
}
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
return argv
|
||||
}
|
||||
|
||||
@@ -74,11 +74,12 @@ func (zypperBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine fu
|
||||
if opts.DryRun {
|
||||
return Run(ctx, []string{"zypper", "--non-interactive", "--dry-run", "update"}, RunOptions{OnLine: onLine})
|
||||
}
|
||||
names := pickTargetNames(opts.Targets, "zypper", true)
|
||||
if len(names) == 0 {
|
||||
if !BackendHasTargets(zypperBackend{}, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return nil
|
||||
}
|
||||
privesc := privescBin(opts.UseSudo)
|
||||
argv := append([]string{privesc, "zypper", "--non-interactive", "update"}, names...)
|
||||
return Run(ctx, argv, RunOptions{OnLine: onLine})
|
||||
return Run(ctx, zypperUpgradeArgv(opts), RunOptions{OnLine: onLine, AttachStdio: opts.AttachStdio})
|
||||
}
|
||||
|
||||
func zypperUpgradeArgv(opts UpgradeOptions) []string {
|
||||
return privilegedArgv(opts, "zypper", "--non-interactive", "update")
|
||||
}
|
||||
|
||||
@@ -14,8 +14,9 @@ import (
|
||||
)
|
||||
|
||||
type RunOptions struct {
|
||||
Env []string
|
||||
OnLine func(string)
|
||||
Env []string
|
||||
OnLine func(string)
|
||||
AttachStdio bool
|
||||
}
|
||||
|
||||
func Run(ctx context.Context, argv []string, opts RunOptions) error {
|
||||
@@ -27,6 +28,19 @@ func Run(ctx context.Context, argv []string, opts RunOptions) error {
|
||||
if len(opts.Env) > 0 {
|
||||
cmd.Env = append(cmd.Environ(), opts.Env...)
|
||||
}
|
||||
if opts.AttachStdio {
|
||||
cmd.Cancel = func() error {
|
||||
if cmd.Process == nil {
|
||||
return nil
|
||||
}
|
||||
return cmd.Process.Kill()
|
||||
}
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
return cmd.Run()
|
||||
}
|
||||
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
|
||||
cmd.Cancel = func() error {
|
||||
if cmd.Process == nil {
|
||||
|
||||
@@ -16,11 +16,12 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
defaultIntervalSeconds = 30 * 60
|
||||
minIntervalSeconds = 5 * 60
|
||||
recentLogCapacity = 200
|
||||
checkTimeout = 5 * time.Minute
|
||||
upgradeTimeout = 30 * time.Minute
|
||||
defaultIntervalSeconds = 30 * 60
|
||||
minIntervalSeconds = 5 * 60
|
||||
recentLogCapacity = 200
|
||||
checkTimeout = 5 * time.Minute
|
||||
upgradeTimeout = 30 * time.Minute
|
||||
postUpgradeCompleteDelay = 3 * time.Second
|
||||
)
|
||||
|
||||
type Manager struct {
|
||||
@@ -310,18 +311,18 @@ func (m *Manager) runUpgrade(ctx context.Context, opts UpgradeOptions) {
|
||||
return
|
||||
}
|
||||
|
||||
backends := upgradeBackends(m.selection, opts)
|
||||
if len(backends) == 0 {
|
||||
m.setError(ErrCodeNoBackend, "no backend selected for upgrade")
|
||||
return
|
||||
}
|
||||
|
||||
if len(opts.Targets) == 0 {
|
||||
m.mu.RLock()
|
||||
opts.Targets = append([]Package(nil), m.state.Packages...)
|
||||
m.mu.RUnlock()
|
||||
}
|
||||
|
||||
backends := upgradeBackends(m.selection, opts)
|
||||
if len(backends) == 0 {
|
||||
m.setError(ErrCodeNoBackend, "no backend selected for upgrade")
|
||||
return
|
||||
}
|
||||
|
||||
opID := fmt.Sprintf("op-%d", time.Now().UnixNano())
|
||||
m.mu.Lock()
|
||||
m.state.Phase = PhaseUpgrading
|
||||
@@ -351,13 +352,7 @@ func (m *Manager) runUpgrade(ctx context.Context, opts UpgradeOptions) {
|
||||
}
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.state.Phase = PhaseIdle
|
||||
m.state.OperationID = ""
|
||||
m.state.OperationStarted = 0
|
||||
m.mu.Unlock()
|
||||
m.markDirty()
|
||||
go m.runRefresh(context.Background())
|
||||
m.finishSuccessfulUpgrade(true)
|
||||
}
|
||||
|
||||
func (m *Manager) runCustomUpgrade(ctx context.Context, command, terminalOverride string) {
|
||||
@@ -395,10 +390,29 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, command, terminalOverrid
|
||||
return
|
||||
}
|
||||
|
||||
m.finishSuccessfulUpgrade(false)
|
||||
}
|
||||
|
||||
func (m *Manager) finishSuccessfulUpgrade(clearPackages bool) {
|
||||
m.appendLog("Upgrade complete.")
|
||||
|
||||
timer := time.NewTimer(postUpgradeCompleteDelay)
|
||||
defer timer.Stop()
|
||||
|
||||
select {
|
||||
case <-m.stopChan:
|
||||
return
|
||||
case <-timer.C:
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
m.state.Phase = PhaseIdle
|
||||
m.state.OperationID = ""
|
||||
m.state.OperationStarted = 0
|
||||
if clearPackages {
|
||||
m.state.Packages = m.state.Packages[:0]
|
||||
m.state.Count = 0
|
||||
}
|
||||
m.mu.Unlock()
|
||||
m.markDirty()
|
||||
go m.runRefresh(context.Background())
|
||||
@@ -407,18 +421,25 @@ func (m *Manager) runCustomUpgrade(ctx context.Context, command, terminalOverrid
|
||||
func upgradeBackends(sel Selection, opts UpgradeOptions) []Backend {
|
||||
var out []Backend
|
||||
if sel.System != nil {
|
||||
out = append(out, sel.System)
|
||||
out = appendUpgradeBackend(out, sel.System, opts)
|
||||
}
|
||||
for _, b := range sel.Overlay {
|
||||
switch {
|
||||
case b.Repo() == RepoFlatpak && !opts.IncludeFlatpak:
|
||||
continue
|
||||
}
|
||||
out = append(out, b)
|
||||
out = appendUpgradeBackend(out, b, opts)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func appendUpgradeBackend(out []Backend, b Backend, opts UpgradeOptions) []Backend {
|
||||
if !BackendHasTargets(b, opts.Targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return out
|
||||
}
|
||||
return append(out, b)
|
||||
}
|
||||
|
||||
func (m *Manager) appendLog(line string) {
|
||||
m.mu.Lock()
|
||||
if cap(m.state.RecentLog) == 0 {
|
||||
|
||||
60
core/internal/server/sysupdate/targets.go
Normal file
60
core/internal/server/sysupdate/targets.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package sysupdate
|
||||
|
||||
func BackendHasTargets(b Backend, targets []Package, includeAUR, includeFlatpak bool) bool {
|
||||
if b == nil || len(targets) == 0 {
|
||||
return false
|
||||
}
|
||||
id := b.ID()
|
||||
repo := b.Repo()
|
||||
for _, p := range targets {
|
||||
switch p.Repo {
|
||||
case RepoFlatpak:
|
||||
if !includeFlatpak {
|
||||
continue
|
||||
}
|
||||
case RepoAUR:
|
||||
if !includeAUR {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
switch repo {
|
||||
case RepoFlatpak:
|
||||
if p.Repo == RepoFlatpak || p.Backend == id {
|
||||
return true
|
||||
}
|
||||
case RepoOSTree:
|
||||
if p.Repo == RepoOSTree || p.Backend == id {
|
||||
return true
|
||||
}
|
||||
default:
|
||||
if p.Backend == id {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func UpgradeNeedsPrivilege(backends []Backend, targets []Package, opts UpgradeOptions) bool {
|
||||
if opts.DryRun {
|
||||
return false
|
||||
}
|
||||
for _, b := range backends {
|
||||
if b == nil {
|
||||
continue
|
||||
}
|
||||
if b.NeedsAuth() && BackendHasTargets(b, targets, opts.IncludeAUR, opts.IncludeFlatpak) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func privilegedArgv(opts UpgradeOptions, argv ...string) []string {
|
||||
privesc := privescBin(opts.UseSudo)
|
||||
out := make([]string, 0, len(argv)+1)
|
||||
out = append(out, privesc)
|
||||
out = append(out, argv...)
|
||||
return out
|
||||
}
|
||||
@@ -77,6 +77,7 @@ type UpgradeOptions struct {
|
||||
IncludeAUR bool
|
||||
DryRun bool
|
||||
UseSudo bool
|
||||
AttachStdio bool
|
||||
CustomCommand string
|
||||
Terminal string
|
||||
Targets []Package
|
||||
|
||||
147
core/internal/server/sysupdate/upgrade_commands_test.go
Normal file
147
core/internal/server/sysupdate/upgrade_commands_test.go
Normal file
@@ -0,0 +1,147 @@
|
||||
package sysupdate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestUpgradeCommandBuilders(t *testing.T) {
|
||||
pkexecOpts := UpgradeOptions{UseSudo: false}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
got []string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "dnf full upgrade",
|
||||
got: dnfUpgradeArgv("dnf5", pkexecOpts),
|
||||
want: []string{"pkexec", "dnf5", "upgrade", "--refresh", "-y"},
|
||||
},
|
||||
{
|
||||
name: "apt full upgrade",
|
||||
got: aptUpgradeArgv("apt-get", pkexecOpts),
|
||||
want: []string{"pkexec", "env", "DEBIAN_FRONTEND=noninteractive", "LC_ALL=C", "apt-get", "upgrade", "-y"},
|
||||
},
|
||||
{
|
||||
name: "zypper full update",
|
||||
got: zypperUpgradeArgv(pkexecOpts),
|
||||
want: []string{"pkexec", "zypper", "--non-interactive", "update"},
|
||||
},
|
||||
{
|
||||
name: "pacman full sync upgrade",
|
||||
got: pacmanUpgradeArgv(pkexecOpts),
|
||||
want: []string{"pkexec", "pacman", "-Syu", "--noconfirm", "--needed"},
|
||||
},
|
||||
{
|
||||
name: "aur helper full update with aur",
|
||||
got: archHelperUpgradeArgv("paru", true),
|
||||
want: []string{"paru", "-Syu", "--noconfirm", "--needed"},
|
||||
},
|
||||
{
|
||||
name: "aur helper repo-only full update",
|
||||
got: archHelperUpgradeArgv("yay", false),
|
||||
want: []string{"yay", "-Syu", "--noconfirm", "--needed", "--repo"},
|
||||
},
|
||||
{
|
||||
name: "flatpak full update",
|
||||
got: flatpakUpgradeArgv(),
|
||||
want: []string{"flatpak", "update", "-y", "--noninteractive"},
|
||||
},
|
||||
{
|
||||
name: "rpm-ostree upgrade",
|
||||
got: rpmOstreeUpgradeArgv(UpgradeOptions{}),
|
||||
want: []string{"rpm-ostree", "upgrade"},
|
||||
},
|
||||
{
|
||||
name: "rpm-ostree check",
|
||||
got: rpmOstreeUpgradeArgv(UpgradeOptions{DryRun: true}),
|
||||
want: []string{"rpm-ostree", "upgrade", "--check"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if !reflect.DeepEqual(tt.got, tt.want) {
|
||||
t.Fatalf("argv = %#v, want %#v", tt.got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendHasTargetsRespectsBackendAndOptions(t *testing.T) {
|
||||
targets := []Package{
|
||||
{Name: "bash.x86_64", Repo: RepoSystem, Backend: "dnf5"},
|
||||
{Name: "google-chrome", Repo: RepoAUR, Backend: "paru"},
|
||||
{Name: "Discord", Repo: RepoFlatpak, Backend: "flatpak"},
|
||||
{Name: "silverblue", Repo: RepoOSTree, Backend: "rpm-ostree"},
|
||||
}
|
||||
|
||||
if !BackendHasTargets(dnfBackend{bin: "dnf5"}, targets, true, true) {
|
||||
t.Fatal("dnf5 target was not detected")
|
||||
}
|
||||
if BackendHasTargets(dnfBackend{bin: "dnf"}, targets, true, true) {
|
||||
t.Fatal("dnf target should not match dnf5 package targets")
|
||||
}
|
||||
if !BackendHasTargets(archHelperBackend{id: "paru"}, targets, true, true) {
|
||||
t.Fatal("AUR helper target was not detected")
|
||||
}
|
||||
if BackendHasTargets(archHelperBackend{id: "paru"}, targets, false, true) {
|
||||
t.Fatal("AUR helper should not match AUR-only target when AUR is disabled")
|
||||
}
|
||||
if !BackendHasTargets(flatpakBackend{}, targets, true, true) {
|
||||
t.Fatal("Flatpak target was not detected")
|
||||
}
|
||||
if BackendHasTargets(flatpakBackend{}, targets, true, false) {
|
||||
t.Fatal("Flatpak target should not match when Flatpak is disabled")
|
||||
}
|
||||
if !BackendHasTargets(rpmOstreeBackend{}, targets, true, true) {
|
||||
t.Fatal("rpm-ostree target was not detected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeNeedsPrivilegeSkipsFlatpakOnly(t *testing.T) {
|
||||
backends := []Backend{dnfBackend{bin: "dnf5"}, flatpakBackend{}}
|
||||
opts := UpgradeOptions{IncludeAUR: true, IncludeFlatpak: true}
|
||||
|
||||
flatpakOnly := []Package{{Name: "Discord", Repo: RepoFlatpak, Backend: "flatpak"}}
|
||||
if UpgradeNeedsPrivilege(backends, flatpakOnly, opts) {
|
||||
t.Fatal("Flatpak-only updates should not need privileged auth")
|
||||
}
|
||||
|
||||
mixed := []Package{
|
||||
{Name: "bash.x86_64", Repo: RepoSystem, Backend: "dnf5"},
|
||||
{Name: "Discord", Repo: RepoFlatpak, Backend: "flatpak"},
|
||||
}
|
||||
if !UpgradeNeedsPrivilege(backends, mixed, opts) {
|
||||
t.Fatal("mixed system updates should need privileged auth")
|
||||
}
|
||||
|
||||
opts.DryRun = true
|
||||
if UpgradeNeedsPrivilege(backends, mixed, opts) {
|
||||
t.Fatal("dry-run updates should not need privileged auth")
|
||||
}
|
||||
}
|
||||
|
||||
func TestUpgradeBackendsFiltersFlatpakOnly(t *testing.T) {
|
||||
sel := Selection{
|
||||
System: dnfBackend{bin: "dnf5"},
|
||||
Overlay: []Backend{flatpakBackend{}},
|
||||
}
|
||||
opts := UpgradeOptions{
|
||||
IncludeAUR: true,
|
||||
IncludeFlatpak: true,
|
||||
Targets: []Package{{Name: "Discord", Repo: RepoFlatpak, Backend: "flatpak"}},
|
||||
}
|
||||
|
||||
got := upgradeBackends(sel, opts)
|
||||
if len(got) != 1 || got[0].ID() != "flatpak" {
|
||||
t.Fatalf("upgradeBackends(flatpak-only) = %#v, want only flatpak", got)
|
||||
}
|
||||
|
||||
opts.Targets = append(opts.Targets, Package{Name: "bash.x86_64", Repo: RepoSystem, Backend: "dnf5"})
|
||||
got = upgradeBackends(sel, opts)
|
||||
if len(got) != 2 || got[0].ID() != "dnf5" || got[1].ID() != "flatpak" {
|
||||
t.Fatalf("upgradeBackends(mixed) = %#v, want dnf5 then flatpak", got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user