1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-30 01:22:06 -04:00

Revert "system updater: make all distros use terminal"

This reverts commit 1467f5dba9.
This commit is contained in:
bbedward
2026-04-29 14:56:54 -04:00
parent 1467f5dba9
commit 3b96c6ab22
15 changed files with 222 additions and 98 deletions

View File

@@ -7,8 +7,6 @@ This file is more of a quick reference so I know what to account for before next
- Terminal mux
- Locale overrides
- new neovim theming
- system upder overhaul
- New Log system, remember to update all plugins after release as it breaks compat
# 1.4.0

View File

@@ -6,7 +6,6 @@ import (
"encoding/json"
"fmt"
"os"
"os/exec"
"strings"
"time"
@@ -184,20 +183,10 @@ func runSystemUpdateApply() {
DryRun: sysUpdateDry,
}
onLine := func(line string) { fmt.Println(line) }
for _, b := range backends {
cmd, err := b.UpgradeCommand(opts)
if err != nil {
log.Fatalf("%s: %v", b.ID(), err)
}
if cmd == "" {
continue
}
fmt.Printf("\n== %s ==\n$ %s\n\n", b.DisplayName(), cmd)
shell := exec.CommandContext(ctx, "sh", "-c", cmd)
shell.Stdin = os.Stdin
shell.Stdout = os.Stdout
shell.Stderr = os.Stderr
if err := shell.Run(); err != nil {
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)
}
}

View File

@@ -12,8 +12,9 @@ type Backend interface {
Repo() RepoKind
IsAvailable(ctx context.Context) bool
NeedsAuth() bool
RunsInTerminal() bool
CheckUpdates(ctx context.Context) ([]Package, error)
UpgradeCommand(opts UpgradeOptions) (string, error)
Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error
}
type Selection struct {
@@ -40,6 +41,7 @@ func (s Selection) Info() []BackendInfo {
DisplayName: b.DisplayName(),
Repo: b.Repo(),
NeedsAuth: b.NeedsAuth(),
RunsInTerminal: b.RunsInTerminal(),
})
}
return out

View File

@@ -19,6 +19,7 @@ func (aptBackend) ID() string { return "apt" }
func (aptBackend) DisplayName() string { return "APT" }
func (aptBackend) Repo() RepoKind { return RepoSystem }
func (aptBackend) NeedsAuth() bool { return true }
func (aptBackend) RunsInTerminal() bool { return false }
func (aptBackend) IsAvailable(_ context.Context) bool {
return commandExists("apt") || commandExists("apt-get")
}
@@ -33,16 +34,19 @@ func (aptBackend) CheckUpdates(ctx context.Context) ([]Package, error) {
return parseAptUpgradable(string(out)), nil
}
func (aptBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (aptBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
bin := "apt-get"
if !commandExists(bin) {
bin = "apt"
}
env := "DEBIAN_FRONTEND=noninteractive LC_ALL=C "
if opts.DryRun {
return env + bin + " upgrade --dry-run", nil
return Run(ctx, []string{bin, "upgrade", "--dry-run"}, RunOptions{
Env: []string{"DEBIAN_FRONTEND=noninteractive", "LC_ALL=C"},
OnLine: onLine,
})
}
return "sudo " + env + bin + " upgrade -y", nil
argv := []string{"pkexec", "env", "DEBIAN_FRONTEND=noninteractive", "LC_ALL=C", bin, "upgrade", "-y"}
return Run(ctx, argv, RunOptions{OnLine: onLine})
}
func parseAptUpgradable(text string) []Package {

View File

@@ -3,7 +3,6 @@ package sysupdate
import (
"context"
"errors"
"fmt"
"os/exec"
"strings"
)
@@ -21,6 +20,7 @@ func (b dnfBackend) ID() string { return b.bin }
func (b dnfBackend) DisplayName() string { return strings.ToUpper(b.bin) }
func (b dnfBackend) Repo() RepoKind { return RepoSystem }
func (b dnfBackend) NeedsAuth() bool { return true }
func (b dnfBackend) RunsInTerminal() bool { return false }
func (b dnfBackend) IsAvailable(ctx context.Context) bool {
if !commandExists(b.bin) {
@@ -41,11 +41,11 @@ func (b dnfBackend) CheckUpdates(ctx context.Context) ([]Package, error) {
return parseDnfList(out, b.bin, installed), nil
}
func (b dnfBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (b dnfBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
if opts.DryRun {
return fmt.Sprintf("%s upgrade --assumeno", b.bin), nil
return Run(ctx, []string{b.bin, "upgrade", "--assumeno"}, RunOptions{OnLine: onLine})
}
return fmt.Sprintf("sudo %s upgrade -y", b.bin), nil
return Run(ctx, []string{"pkexec", b.bin, "upgrade", "-y"}, RunOptions{OnLine: onLine})
}
func dnfListUpgrades(ctx context.Context, bin string) (string, error) {

View File

@@ -16,6 +16,7 @@ func (flatpakBackend) ID() string { return "flatpak" }
func (flatpakBackend) DisplayName() string { return "Flatpak" }
func (flatpakBackend) Repo() RepoKind { return RepoFlatpak }
func (flatpakBackend) NeedsAuth() bool { return false }
func (flatpakBackend) RunsInTerminal() bool { return false }
func (flatpakBackend) IsAvailable(_ context.Context) bool { return commandExists("flatpak") }
func (flatpakBackend) CheckUpdates(ctx context.Context) ([]Package, error) {
@@ -68,11 +69,12 @@ type flatpakInstalledEntry struct {
commit string
}
func (flatpakBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (flatpakBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
argv := []string{"flatpak", "update", "-y", "--noninteractive"}
if opts.DryRun {
return "flatpak update --no-deploy -y", nil
argv = []string{"flatpak", "update", "--no-deploy", "-y"}
}
return "flatpak update -y --noninteractive", nil
return Run(ctx, argv, RunOptions{OnLine: onLine})
}
func parseFlatpakUpdates(text string, installed map[string]flatpakInstalledEntry) []Package {

View File

@@ -28,6 +28,7 @@ func (pacmanBackend) ID() string { return "pacman" }
func (pacmanBackend) DisplayName() string { return "Pacman" }
func (pacmanBackend) Repo() RepoKind { return RepoSystem }
func (pacmanBackend) NeedsAuth() bool { return true }
func (pacmanBackend) RunsInTerminal() bool { return false }
func (pacmanBackend) IsAvailable(_ context.Context) bool { return commandExists("pacman") }
func (b pacmanBackend) CheckUpdates(ctx context.Context) ([]Package, error) {
@@ -38,11 +39,11 @@ func (b pacmanBackend) CheckUpdates(ctx context.Context) ([]Package, error) {
return parseArchUpdates(out, b.ID(), RepoSystem), nil
}
func (pacmanBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (b pacmanBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
if opts.DryRun {
return "pacman -Sup", nil
return Run(ctx, []string{"pacman", "-Sup"}, RunOptions{OnLine: onLine})
}
return "sudo pacman -Syu --noconfirm", nil
return Run(ctx, []string{"pkexec", "pacman", "-Syu", "--noconfirm"}, RunOptions{OnLine: onLine})
}
type archHelperBackend struct {
@@ -52,6 +53,7 @@ type archHelperBackend struct {
func (b archHelperBackend) ID() string { return b.id }
func (b archHelperBackend) Repo() RepoKind { return RepoSystem }
func (b archHelperBackend) NeedsAuth() bool { return true }
func (b archHelperBackend) RunsInTerminal() bool { return true }
func (b archHelperBackend) IsAvailable(_ context.Context) bool { return commandExists(b.id) }
func (b archHelperBackend) DisplayName() string {
@@ -80,15 +82,20 @@ func (b archHelperBackend) CheckUpdates(ctx context.Context) ([]Package, error)
return pkgs, nil
}
func (b archHelperBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (b archHelperBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
if opts.DryRun {
return fmt.Sprintf("%s -Sup", b.id), nil
return Run(ctx, []string{b.id, "-Sup"}, RunOptions{OnLine: onLine})
}
cmd := fmt.Sprintf("%s -Syu --noconfirm", b.id)
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 -Syu", b.id)
if !opts.IncludeAUR {
cmd += " --repo"
}
return cmd, nil
title := fmt.Sprintf("DMS — System Update (%s)", b.id)
return Run(ctx, wrapInTerminal(term, title, cmd), RunOptions{OnLine: onLine})
}
func pacmanRepoUpdates(ctx context.Context) (string, error) {

View File

@@ -19,6 +19,7 @@ func (rpmOstreeBackend) ID() string { return "rpm-ostree" }
func (rpmOstreeBackend) DisplayName() string { return "rpm-ostree" }
func (rpmOstreeBackend) Repo() RepoKind { return RepoOSTree }
func (rpmOstreeBackend) NeedsAuth() bool { return true }
func (rpmOstreeBackend) RunsInTerminal() bool { return false }
func (b rpmOstreeBackend) IsAvailable(ctx context.Context) bool {
if !commandExists("rpm-ostree") {
@@ -115,9 +116,10 @@ func bootedDeployment(deps []ostreeDeployment) *ostreeDeployment {
return nil
}
func (rpmOstreeBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (rpmOstreeBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
argv := []string{"rpm-ostree", "upgrade"}
if opts.DryRun {
return "rpm-ostree upgrade --check", nil
argv = append(argv, "--check")
}
return "rpm-ostree upgrade", nil
return Run(ctx, argv, RunOptions{OnLine: onLine})
}

View File

@@ -17,6 +17,7 @@ func (zypperBackend) ID() string { return "zypper" }
func (zypperBackend) DisplayName() string { return "Zypper" }
func (zypperBackend) Repo() RepoKind { return RepoSystem }
func (zypperBackend) NeedsAuth() bool { return true }
func (zypperBackend) RunsInTerminal() bool { return false }
func (zypperBackend) IsAvailable(_ context.Context) bool { return commandExists("zypper") }
type zypperUpdateList struct {
@@ -69,9 +70,9 @@ func parseZypperXML(out []byte) ([]Package, error) {
return pkgs, nil
}
func (zypperBackend) UpgradeCommand(opts UpgradeOptions) (string, error) {
func (zypperBackend) Upgrade(ctx context.Context, opts UpgradeOptions, onLine func(string)) error {
if opts.DryRun {
return "zypper --non-interactive --dry-run update", nil
return Run(ctx, []string{"zypper", "--non-interactive", "--dry-run", "update"}, RunOptions{OnLine: onLine})
}
return "sudo zypper --non-interactive update", nil
return Run(ctx, []string{"pkexec", "zypper", "--non-interactive", "update"}, RunOptions{OnLine: onLine})
}

View File

@@ -1,18 +1,63 @@
package sysupdate
import (
"bufio"
"context"
"fmt"
"io"
"os"
"os/exec"
"sync"
)
func Run(ctx context.Context, argv []string) error {
type RunOptions struct {
Env []string
OnLine func(string)
}
func Run(ctx context.Context, argv []string, opts RunOptions) error {
if len(argv) == 0 {
return fmt.Errorf("sysupdate.Run: empty argv")
}
cmd := exec.CommandContext(ctx, argv[0], argv[1:]...)
return cmd.Run()
if len(opts.Env) > 0 {
cmd.Env = append(cmd.Environ(), opts.Env...)
}
stdout, err := cmd.StdoutPipe()
if err != nil {
return err
}
stderr, err := cmd.StderrPipe()
if err != nil {
return err
}
if err := cmd.Start(); err != nil {
return err
}
var wg sync.WaitGroup
wg.Add(2)
go pump(stdout, opts.OnLine, &wg)
go pump(stderr, opts.OnLine, &wg)
wg.Wait()
return cmd.Wait()
}
func pump(r io.Reader, onLine func(string), wg *sync.WaitGroup) {
defer wg.Done()
if onLine == nil {
_, _ = io.Copy(io.Discard, r)
return
}
scanner := bufio.NewScanner(r)
scanner.Buffer(make([]byte, 64*1024), 1024*1024)
for scanner.Scan() {
onLine(scanner.Text())
}
}
func Capture(ctx context.Context, argv []string) (string, error) {

View File

@@ -18,6 +18,7 @@ import (
const (
defaultIntervalSeconds = 30 * 60
minIntervalSeconds = 5 * 60
recentLogCapacity = 200
checkTimeout = 5 * time.Minute
upgradeTimeout = 30 * time.Minute
)
@@ -239,6 +240,7 @@ func (m *Manager) runRefresh(parent context.Context) {
}
m.state.Phase = PhaseRefreshing
m.state.Error = nil
m.state.RecentLog = nil
m.mu.Unlock()
m.markDirty()
@@ -296,13 +298,57 @@ func (m *Manager) runUpgrade(ctx context.Context, opts UpgradeOptions) {
m.opMu.Unlock()
}()
combined, err := buildBundledCommand(m.selection, opts)
if err != nil {
m.setError(ErrCodeNoBackend, err.Error())
if opts.CustomCommand != "" {
m.runCustomUpgrade(ctx, opts.CustomCommand, opts.Terminal)
return
}
term := findTerminal(opts.Terminal)
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
m.state.OperationID = opID
m.state.OperationStarted = time.Now().Unix()
m.state.RecentLog = m.state.RecentLog[:0]
m.state.Error = nil
m.mu.Unlock()
m.markDirty()
onLine := func(line string) { m.appendLog(line) }
for _, b := range backends {
m.appendLog(fmt.Sprintf("== %s ==", b.DisplayName()))
if err := b.Upgrade(ctx, opts, onLine); err != nil {
code := ErrCodeBackendFailed
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
code = ErrCodeTimeout
} else if errors.Is(ctx.Err(), context.Canceled) {
code = ErrCodeCancelled
}
m.mu.Lock()
m.state.Phase = PhaseError
m.state.Error = &ErrorInfo{Code: code, Message: fmt.Sprintf("%s: %v", b.ID(), err)}
m.mu.Unlock()
m.markDirty()
return
}
}
m.mu.Lock()
m.state.Phase = PhaseIdle
m.state.OperationID = ""
m.state.OperationStarted = 0
m.mu.Unlock()
m.markDirty()
go m.runRefresh(context.Background())
}
func (m *Manager) runCustomUpgrade(ctx context.Context, command, terminalOverride string) {
term := findTerminal(terminalOverride)
if term == "" {
m.setError(ErrCodeBackendFailed, "no terminal found (pick one in DMS settings, set $TERMINAL, or install kitty/ghostty/foot/alacritty)")
return
@@ -313,12 +359,14 @@ func (m *Manager) runUpgrade(ctx context.Context, opts UpgradeOptions) {
m.state.Phase = PhaseUpgrading
m.state.OperationID = opID
m.state.OperationStarted = time.Now().Unix()
m.state.RecentLog = m.state.RecentLog[:0]
m.state.Error = nil
m.mu.Unlock()
m.markDirty()
argv := wrapInTerminal(term, "DMS — System Update", combined)
if err := Run(ctx, argv); err != nil {
onLine := func(line string) { m.appendLog(line) }
argv := wrapInTerminal(term, "DMS — System Update (custom)", command)
if err := Run(ctx, argv, RunOptions{OnLine: onLine}); err != nil {
code := ErrCodeBackendFailed
switch {
case errors.Is(ctx.Err(), context.DeadlineExceeded):
@@ -343,31 +391,6 @@ func (m *Manager) runUpgrade(ctx context.Context, opts UpgradeOptions) {
go m.runRefresh(context.Background())
}
func buildBundledCommand(sel Selection, opts UpgradeOptions) (string, error) {
if opts.CustomCommand != "" {
return opts.CustomCommand, nil
}
backends := upgradeBackends(sel, opts)
if len(backends) == 0 {
return "", errors.New("no backend selected for upgrade")
}
parts := make([]string, 0, len(backends))
for _, b := range backends {
cmd, err := b.UpgradeCommand(opts)
if err != nil {
return "", fmt.Errorf("%s: %w", b.ID(), err)
}
if cmd == "" {
continue
}
parts = append(parts, cmd)
}
if len(parts) == 0 {
return "", errors.New("no backend produced an upgrade command")
}
return strings.Join(parts, " && "), nil
}
func upgradeBackends(sel Selection, opts UpgradeOptions) []Backend {
var out []Backend
if sel.System != nil {
@@ -383,6 +406,20 @@ func upgradeBackends(sel Selection, opts UpgradeOptions) []Backend {
return out
}
func (m *Manager) appendLog(line string) {
m.mu.Lock()
if cap(m.state.RecentLog) == 0 {
m.state.RecentLog = make([]string, 0, recentLogCapacity)
}
if len(m.state.RecentLog) >= recentLogCapacity {
copy(m.state.RecentLog, m.state.RecentLog[1:])
m.state.RecentLog = m.state.RecentLog[:recentLogCapacity-1]
}
m.state.RecentLog = append(m.state.RecentLog, line)
m.mu.Unlock()
m.markDirty()
}
func (m *Manager) setError(code ErrorCode, msg string) {
m.mu.Lock()
m.state.Phase = PhaseError
@@ -421,6 +458,7 @@ func cloneState(s State) State {
out := s
out.Backends = append([]BackendInfo(nil), s.Backends...)
out.Packages = append([]Package(nil), s.Packages...)
out.RecentLog = append([]string(nil), s.RecentLog...)
if s.Error != nil {
errCopy := *s.Error
out.Error = &errCopy

View File

@@ -45,6 +45,7 @@ type BackendInfo struct {
DisplayName string `json:"displayName"`
Repo RepoKind `json:"repo"`
NeedsAuth bool `json:"needsAuth"`
RunsInTerminal bool `json:"runsInTerminal"`
}
type ErrorInfo struct {
@@ -66,6 +67,7 @@ type State struct {
NextCheckUnix int64 `json:"nextCheckUnix,omitempty"`
OperationID string `json:"operationId,omitempty"`
OperationStarted int64 `json:"operationStartedUnix,omitempty"`
RecentLog []string `json:"recentLog,omitempty"`
Error *ErrorInfo `json:"error,omitempty"`
}

View File

@@ -57,6 +57,8 @@ DankPopout {
color: "transparent"
focus: true
readonly property bool hasTerminalBackend: (SystemUpdateService.backends || []).some(b => b.runsInTerminal === true)
Keys.onPressed: event => {
if (event.key === Qt.Key_Escape) {
systemUpdatePopout.close();
@@ -206,9 +208,13 @@ DankPopout {
includeAUR: SettingsData.updaterAllowAUR,
terminal: SessionData.terminalOverride
};
if (updaterPanel.hasTerminalBackend) {
systemUpdatePopout._reopenAfterUpgrade = true;
SystemUpdateService.runUpdates(opts);
systemUpdatePopout.close();
return;
}
SystemUpdateService.runUpdates(opts);
}
}
@@ -377,7 +383,7 @@ DankPopout {
anchors.fill: parent
anchors.margins: Theme.spacingM
spacing: Theme.spacingS
visible: SystemUpdateService.isUpgrading
visible: SystemUpdateService.isUpgrading && updaterPanel.hasTerminalBackend
DankIcon {
anchors.horizontalCenter: parent.horizontalCenter
@@ -397,13 +403,38 @@ DankPopout {
StyledText {
width: parent.width
text: I18n.tr("See the terminal window for prompts. This popout will return when the upgrade exits.")
text: I18n.tr("AUR helpers are interactive — see the terminal window for prompts. This popout will return to idle when the upgrade exits.")
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceVariantText
wrapMode: Text.WordWrap
horizontalAlignment: Text.AlignHCenter
}
}
DankFlickable {
anchors.fill: parent
anchors.margins: Theme.spacingM
visible: SystemUpdateService.isUpgrading && !updaterPanel.hasTerminalBackend
contentWidth: width
contentHeight: logText.implicitHeight
clip: true
onContentHeightChanged: {
if (contentHeight > height) {
contentY = contentHeight - height;
}
}
StyledText {
id: logText
width: parent.width
text: (SystemUpdateService.recentLog || []).join("\n")
font.family: Theme.monoFontFamily || "monospace"
font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText
wrapMode: Text.NoWrap
}
}
}
}
}

View File

@@ -9,6 +9,7 @@ Rectangle {
property string iconName: ""
property string text: ""
property bool isDestructive: false
property bool enabled: true
signal triggered

View File

@@ -24,6 +24,7 @@ Singleton {
property string distributionPretty: ""
property string pkgManager: ""
property bool distributionSupported: false
property var recentLog: []
property int intervalSeconds: 1800
property int lastCheckUnix: 0
property int nextCheckUnix: 0
@@ -88,6 +89,7 @@ Singleton {
distribution = data.distro || "";
distributionPretty = data.distroPretty || "";
distributionSupported = (backends.length > 0);
recentLog = data.recentLog || [];
intervalSeconds = data.intervalSeconds || 1800;
lastCheckUnix = data.lastCheckUnix || 0;
nextCheckUnix = data.nextCheckUnix || 0;