From 5aa2e3f0d450f99b9b8db33a399df95d2eb51bf2 Mon Sep 17 00:00:00 2001 From: purian23 Date: Fri, 23 Oct 2026 00:26:01 -0400 Subject: [PATCH] systemd: start enabled service on restart when idle Port 2.0 (cherry picked from commit 5a20a8f16259faf38c671141e1be64aecb7477d0) --- core/cmd/dms/systemd_restart.go | 29 ++++++++++++- core/cmd/dms/systemd_restart_test.go | 61 ++++++++++++++++++++++++++++ core/go.mod | 2 +- core/go.sum | 4 +- flake.nix | 2 +- 5 files changed, 93 insertions(+), 5 deletions(-) create mode 100644 core/cmd/dms/systemd_restart_test.go diff --git a/core/cmd/dms/systemd_restart.go b/core/cmd/dms/systemd_restart.go index bffd1e333..bbcc313f4 100644 --- a/core/cmd/dms/systemd_restart.go +++ b/core/cmd/dms/systemd_restart.go @@ -20,9 +20,16 @@ var runSystemctl = func(args ...string) error { return err } +// Assigned in init: shellApp's config refers back to trySystemdRestart. +var shellRunning func() bool + +func init() { + shellRunning = func() bool { return len(shellApp.PIDs()) > 0 } +} + func trySystemdRestart() (bool, error) { if err := runSystemctl("--user", "is-active", "--quiet", dmsSystemdUnit); err != nil { - return false, nil + return trySystemdStart() } if err := runSystemctl("--user", "restart", dmsSystemdUnit); err != nil { @@ -30,3 +37,23 @@ func trySystemdRestart() (bool, error) { } return true, nil } + +// With nothing running, restart would spawn an unmanaged daemon that blocks +// the unit once the session target starts it. A running hand-started +// instance is left to the plain restart path. +func trySystemdStart() (bool, error) { + if shellRunning() { + return false, nil + } + if err := runSystemctl("--user", "is-enabled", "--quiet", dmsSystemdUnit); err != nil { + return false, nil + } + if err := runSystemctl("--user", "is-active", "--quiet", "graphical-session.target"); err != nil { + return false, nil + } + + if err := runSystemctl("--user", "start", dmsSystemdUnit); err != nil { + return true, fmt.Errorf("starting %s: %w", dmsSystemdUnit, err) + } + return true, nil +} diff --git a/core/cmd/dms/systemd_restart_test.go b/core/cmd/dms/systemd_restart_test.go new file mode 100644 index 000000000..9fd05e733 --- /dev/null +++ b/core/cmd/dms/systemd_restart_test.go @@ -0,0 +1,61 @@ +package main + +import ( + "errors" + "slices" + "strings" + "testing" +) + +func TestTrySystemdRestart(t *testing.T) { + tests := []struct { + name string + running bool + ok []string + wantHandled bool + wantCall string + }{ + {"active unit restarts", false, []string{"is-active dms.service", "restart"}, true, "restart dms.service"}, + {"enabled inactive unit starts", false, []string{"is-enabled", "is-active graphical-session.target", "start"}, true, "start dms.service"}, + {"hand-started instance keeps plain restart", true, []string{"is-enabled", "is-active graphical-session.target", "start"}, false, ""}, + {"session target not up", false, []string{"is-enabled"}, false, ""}, + {"unit disabled", false, []string{"is-active graphical-session.target"}, false, ""}, + {"no systemd", false, nil, false, ""}, + } + + origSystemctl, origRunning := runSystemctl, shellRunning + t.Cleanup(func() { runSystemctl, shellRunning = origSystemctl, origRunning }) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + shellRunning = func() bool { return tt.running } + var calls []string + runSystemctl = func(args ...string) error { + call := strings.Join(slices.DeleteFunc(slices.Clone(args), func(a string) bool { + return strings.HasPrefix(a, "--") + }), " ") + calls = append(calls, call) + for _, prefix := range tt.ok { + if strings.HasPrefix(call, prefix) { + return nil + } + } + return errors.New("exit status 3") + } + + handled, err := trySystemdRestart() + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + if handled != tt.wantHandled { + t.Fatalf("handled = %v, want %v (calls %q)", handled, tt.wantHandled, calls) + } + if tt.wantCall != "" && !slices.Contains(calls, tt.wantCall) { + t.Fatalf("missing %q in calls %q", tt.wantCall, calls) + } + if !tt.wantHandled && slices.ContainsFunc(calls, func(c string) bool { return strings.HasPrefix(c, "start") }) { + t.Fatalf("unexpected start in calls %q", calls) + } + }) + } +} diff --git a/core/go.mod b/core/go.mod index 74ee9774c..256ed8b29 100644 --- a/core/go.mod +++ b/core/go.mod @@ -9,7 +9,7 @@ require ( github.com/charmbracelet/bubbletea v1.3.10 github.com/charmbracelet/lipgloss v1.1.0 github.com/dlclark/regexp2/v2 v2.9.1 - github.com/duxlabs-dev/dux-go v1.6.3-0.20261020163124-e2ca52bdf500 + github.com/duxlabs-dev/dux-go v1.6.3-0.20261023041949-220eb5ff21dc github.com/duxlabs-dev/dux-gop v1.6.3-0.20261020163819-0e4888f1fcea github.com/fsnotify/fsnotify v1.10.1 github.com/go-git/go-git/v6 v6.0.0-beta.1 diff --git a/core/go.sum b/core/go.sum index c8d4ea323..d1829628b 100644 --- a/core/go.sum +++ b/core/go.sum @@ -97,8 +97,8 @@ github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e h1:vUmf0yez github.com/digitalocean/go-smbios v0.0.0-20180907143718-390a4f403a8e/go.mod h1:YTIHhz/QFSYnu/EhlF2SpU2Uk+32abacUYA5ZPljz1A= github.com/dlclark/regexp2/v2 v2.9.1 h1:q6bZW/NN9VodqMjYXpuic2lZxdHPfFTzGS+MJxIJmKQ= github.com/dlclark/regexp2/v2 v2.9.1/go.mod h1:avUrQvPaLz2DrFNHJF0taWAFFX2C1GMSSoeiqFjcBmU= -github.com/duxlabs-dev/dux-go v1.6.3-0.20261020163124-e2ca52bdf500 h1:y02m7QgoX4rwauY9HePIPGCRDJ/fMirBmgj78IkeUsc= -github.com/duxlabs-dev/dux-go v1.6.3-0.20261020163124-e2ca52bdf500/go.mod h1:b+hBqIgOVAmlFKJ+33SY2pH4kJjXpifNeioPMxwC4Nk= +github.com/duxlabs-dev/dux-go v1.6.3-0.20261023041949-220eb5ff21dc h1:oZhWdM7mlEevEm1zaeOeDfIMQkvtZSW8UNqrAZldbAY= +github.com/duxlabs-dev/dux-go v1.6.3-0.20261023041949-220eb5ff21dc/go.mod h1:b+hBqIgOVAmlFKJ+33SY2pH4kJjXpifNeioPMxwC4Nk= github.com/duxlabs-dev/dux-gop v1.6.3-0.20261020163819-0e4888f1fcea h1:+z9uPtBmpXXBPupB6++/8avPA0weOkzAz7jBoE+yIok= github.com/duxlabs-dev/dux-gop v1.6.3-0.20261020163819-0e4888f1fcea/go.mod h1:IYu4GO7zR61w3+tDmjg71dQDInXe5TcP3lHQqJpZYR0= github.com/ebitengine/purego v0.11.2 h1:WWiTx8rD8zYdOwSj8O/NiNbsOvSAgJMTlyV1XlbUSeU= diff --git a/flake.nix b/flake.nix index 3a76971dc..6d75e2a74 100644 --- a/flake.nix +++ b/flake.nix @@ -111,7 +111,7 @@ inherit version; pname = "dms-shell"; src = ./core; - vendorHash = "sha256-fN7v7DuWaqhtD28cTr0FZUIyW7nUqhQoVuatsL2fuo4="; + vendorHash = "sha256-sKkafJm4PkV/gCdcwdmHVMTnlv8EO9hDk5YDxHwt9/E="; subPackages = [ "cmd/dms" ];