systemd: start enabled service on restart when idle

Port 2.0

(cherry picked from commit 5a20a8f162)
This commit is contained in:
purian23
2026-10-23 04:27:03 +00:00
committed by dux-ci[bot]
parent 11f99cc13f
commit 5aa2e3f0d4
5 changed files with 93 additions and 5 deletions
+28 -1
View File
@@ -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
}
+61
View File
@@ -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)
}
})
}
}
+1 -1
View File
@@ -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
+2 -2
View File
@@ -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=
+1 -1
View File
@@ -111,7 +111,7 @@
inherit version;
pname = "dms-shell";
src = ./core;
vendorHash = "sha256-fN7v7DuWaqhtD28cTr0FZUIyW7nUqhQoVuatsL2fuo4=";
vendorHash = "sha256-sKkafJm4PkV/gCdcwdmHVMTnlv8EO9hDk5YDxHwt9/E=";
subPackages = [ "cmd/dms" ];