Skip to content

Commit 71ab752

Browse files
committed
fix(void): updated dms-greeter stability support
- Updates will land in DMS v1.5.1 and users are advised to hold off on v1.5.0 Related #2788 Port 1.5
1 parent 204ecd0 commit 71ab752

10 files changed

Lines changed: 276 additions & 28 deletions

File tree

core/cmd/dms/commands_greeter.go

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1081,8 +1081,11 @@ func ensureGreetdEnabled() error {
10811081
if !runitServiceInstalled("greetd") {
10821082
return fmt.Errorf("greetd service not found in %s. Please install greetd first", runitSvDir)
10831083
}
1084-
// Seat + runtime-dir setup that logind handles automatically on systemd.
1085-
ensureRunitSeat("_greeter")
1084+
if greeter.IsVoidLinux() {
1085+
ensureVoidLogindGreeter("_greeter")
1086+
} else {
1087+
ensureRunitSeat("_greeter")
1088+
}
10861089
ensureGreetdPamRundir()
10871090
if err := enableRunitService("greetd"); err != nil {
10881091
return fmt.Errorf("failed to enable greetd: %w", err)
@@ -1261,6 +1264,9 @@ func enableGreeter(nonInteractive bool) error {
12611264
if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
12621265
fmt.Printf("⚠ Could not ensure cache directory: %v\n Run: sudo mkdir -p %s && sudo chown root:%s %s && sudo chmod 2770 %s\n", err, greeter.GreeterCacheDir, greeterGroup, greeter.GreeterCacheDir, greeter.GreeterCacheDir)
12631266
}
1267+
if err := greeter.EnsureVoidLogindGreetdCommand(logFunc, ""); err != nil {
1268+
return err
1269+
}
12641270

12651271
if err := ensureGraphicalTarget(); err != nil {
12661272
return err
@@ -1475,12 +1481,23 @@ func extractGreeterWrapperFromCommand(command string) string {
14751481
if len(tokens) == 0 {
14761482
return ""
14771483
}
1478-
wrapper := strings.Trim(tokens[0], "\"")
1484+
wrapperIndex := 0
1485+
if filepath.Base(strings.Trim(tokens[0], "\"")) == "env" {
1486+
wrapperIndex = 1
1487+
for wrapperIndex < len(tokens) && strings.Contains(tokens[wrapperIndex], "=") {
1488+
wrapperIndex++
1489+
}
1490+
}
1491+
if wrapperIndex >= len(tokens) {
1492+
return ""
1493+
}
1494+
1495+
wrapper := strings.Trim(tokens[wrapperIndex], "\"")
14791496
if wrapper == "" {
14801497
return ""
14811498
}
1482-
if len(tokens) > 1 {
1483-
next := strings.Trim(tokens[1], "\"")
1499+
if wrapperIndex+1 < len(tokens) {
1500+
next := strings.Trim(tokens[wrapperIndex+1], "\"")
14841501
if next != "" && (filepath.Base(wrapper) == "bash" || filepath.Base(wrapper) == "sh") && strings.Contains(filepath.Base(next), "dms-greeter") {
14851502
return fmt.Sprintf("%s (script: %s)", wrapper, next)
14861503
}

core/cmd/dms/commands_greeter_test.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,13 @@ func TestGreeterStatusStateDirHonorsExplicitOverrideOnNixOS(t *testing.T) {
101101
}
102102
}
103103

104+
func TestExtractGreeterWrapperFromEnvCommand(t *testing.T) {
105+
command := "env LIBSEAT_BACKEND=logind DMS_VOID=1 /usr/bin/dms-greeter --command niri"
106+
if got := extractGreeterWrapperFromCommand(command); got != "/usr/bin/dms-greeter" {
107+
t.Fatalf("extractGreeterWrapperFromCommand() = %q, want %q", got, "/usr/bin/dms-greeter")
108+
}
109+
}
110+
104111
func TestRejectNixOSGreeterMutationBlocksImperativeCommands(t *testing.T) {
105112
origGreeterIsNixOSFn := greeterIsNixOSFn
106113
greeterIsNixOSFn = func() bool { return true }

core/cmd/dms/init_service.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"os"
77
"strings"
88

9+
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
910
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
1011
)
1112

@@ -83,9 +84,31 @@ func ensureRunitSeat(greeterUser string) {
8384
}
8485
}
8586

86-
// ensureGreetdPamRundir adds pam_rundir to the greetd PAM stack so the post-login
87-
// session gets an XDG_RUNTIME_DIR on systems without logind (Void with seatd).
88-
// Appended outside DMS's managed auth block so it survives `dms greeter sync`.
87+
// ensureVoidLogindGreeter configures the elogind-backed greeter on Void.
88+
func ensureVoidLogindGreeter(greeterUser string) {
89+
for _, service := range []string{"dbus", "elogind"} {
90+
if err := enableRunitService(service); err != nil {
91+
fmt.Printf(" ⚠ could not enable %s: %v\n", service, err)
92+
} else {
93+
fmt.Printf(" ✓ %s enabled\n", service)
94+
}
95+
}
96+
greeter.EnsureVoidGreetdRunScript(func(msg string) { fmt.Println(" " + msg) }, "")
97+
if runitServiceEnabled("seatd") {
98+
if err := disableRunitService("seatd"); err != nil {
99+
fmt.Printf(" ⚠ could not disable seatd: %v\n", err)
100+
} else {
101+
fmt.Println(" ✓ seatd disabled (elogind manages the seat)")
102+
}
103+
}
104+
if err := privesc.Run(context.Background(), "", "usermod", "-aG", "video,input", greeterUser); err != nil {
105+
fmt.Printf(" ⚠ could not add %s to video/input groups: %v\n", greeterUser, err)
106+
} else {
107+
fmt.Printf(" ✓ %s added to video/input groups (elogind manages the seat)\n", greeterUser)
108+
}
109+
}
110+
111+
// ensureGreetdPamRundir provides XDG_RUNTIME_DIR to runit greeter sessions.
89112
func ensureGreetdPamRundir() {
90113
const pamPath = "/etc/pam.d/greetd"
91114
data, err := os.ReadFile(pamPath)

core/internal/distros/void.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ func (v *VoidDistribution) DetectDependenciesWithTerminal(ctx context.Context, w
7272
dependencies = append(dependencies, v.detectAccountsService())
7373
dependencies = append(dependencies, v.detectDBus())
7474
dependencies = append(dependencies, v.detectElogind())
75+
dependencies = append(dependencies, v.detectMesaDri())
7576

7677
if wm == deps.WindowManagerHyprland {
7778
dependencies = append(dependencies, v.detectHyprlandTools()...)
@@ -142,6 +143,10 @@ func (v *VoidDistribution) detectElogind() deps.Dependency {
142143
return v.detectPackage("elogind", "loginctl/logind provider for power management and session tracking", v.packageInstalled("elogind") || v.commandExists("loginctl"))
143144
}
144145

146+
func (v *VoidDistribution) detectMesaDri() deps.Dependency {
147+
return v.detectPackage("mesa-dri", "Mesa DRI/EGL drivers (GPU rendering; compositors find no outputs without it)", v.packageInstalled("mesa-dri"))
148+
}
149+
145150
func (v *VoidDistribution) detectXwaylandSatellite() deps.Dependency {
146151
return v.detectPackage("xwayland-satellite", "Xwayland support", v.packageInstalled("xwayland-satellite"))
147152
}
@@ -172,6 +177,7 @@ func (v *VoidDistribution) GetPackageMappingWithVariants(wm deps.WindowManager,
172177
"accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
173178
"dbus": {Name: "dbus", Repository: RepoTypeSystem},
174179
"elogind": {Name: "elogind", Repository: RepoTypeSystem},
180+
"mesa-dri": {Name: "mesa-dri", Repository: RepoTypeSystem},
175181

176182
"quickshell": {Name: "quickshell", Repository: RepoTypeSystem},
177183
"matugen": {Name: "matugen", Repository: RepoTypeSystem},
@@ -305,6 +311,7 @@ func (v *VoidDistribution) ensureSessionServices(ctx context.Context, sudoPasswo
305311
return nil
306312
}
307313

314+
// D-Bus activation alone starts elogind without its wrapper mounts; the runit service is required.
308315
for _, service := range []string{"dbus", "elogind"} {
309316
if !v.runitServiceInstalled(service) {
310317
v.log(fmt.Sprintf("Warning: %s runit service not found in %s; power/session actions may not work until %s is installed", service, voidRunitSvDir, service))

core/internal/greeter/installer.go

Lines changed: 134 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,52 @@ func ensureRunitSeat(greeterUser, sudoPassword string, logFunc func(string)) {
115115
}
116116
}
117117

118+
// VoidGreetdRunScript orders greetd after dbus/elogind so the first greeter session can't race elogind's runtime-dir setup.
119+
const VoidGreetdRunScript = `#!/bin/sh
120+
sv check dbus >/dev/null || exit 1
121+
sv check elogind >/dev/null || exit 1
122+
exec greetd
123+
`
124+
125+
// EnsureVoidGreetdRunScript rewrites /etc/sv/greetd/run with dbus/elogind ordering (greetd updates restore stock; enable re-asserts).
126+
func EnsureVoidGreetdRunScript(logFunc func(string), sudoPassword string) {
127+
const runPath = "/etc/sv/greetd/run"
128+
if data, err := os.ReadFile(runPath); err == nil && strings.Contains(string(data), "sv check elogind") {
129+
logFunc("✓ greetd run script already waits for elogind")
130+
return
131+
}
132+
script := fmt.Sprintf("cat > %s <<'EOF'\n%sEOF\nchmod 755 %s", runPath, VoidGreetdRunScript, runPath)
133+
if err := privesc.Run(context.Background(), sudoPassword, "sh", "-c", script); err != nil {
134+
logFunc(fmt.Sprintf("⚠ could not update %s: %v", runPath, err))
135+
return
136+
}
137+
logFunc("✓ greetd run script now waits for dbus/elogind")
138+
}
139+
140+
// ensureVoidLogindGreeter configures the elogind-backed greeter on Void.
141+
func ensureVoidLogindGreeter(greeterUser, sudoPassword string, logFunc func(string)) {
142+
for _, service := range []string{"dbus", "elogind"} {
143+
if err := enableRunitService(service, sudoPassword); err != nil {
144+
logFunc(fmt.Sprintf("⚠ could not enable %s: %v", service, err))
145+
} else {
146+
logFunc(fmt.Sprintf("✓ %s enabled", service))
147+
}
148+
}
149+
EnsureVoidGreetdRunScript(logFunc, sudoPassword)
150+
if runitServiceEnabled("seatd") {
151+
if err := disableRunitService("seatd", sudoPassword); err != nil {
152+
logFunc(fmt.Sprintf("⚠ could not disable seatd: %v", err))
153+
} else {
154+
logFunc("✓ seatd disabled (elogind manages the seat)")
155+
}
156+
}
157+
if err := privesc.Run(context.Background(), sudoPassword, "usermod", "-aG", "video,input", greeterUser); err != nil {
158+
logFunc(fmt.Sprintf("⚠ could not add %s to video/input groups: %v", greeterUser, err))
159+
} else {
160+
logFunc(fmt.Sprintf("✓ %s added to video/input groups (elogind manages the seat)", greeterUser))
161+
}
162+
}
163+
118164
func ensureGreetdPamRundir(sudoPassword string, logFunc func(string)) {
119165
const pamPath = "/etc/pam.d/greetd"
120166
data, err := os.ReadFile(pamPath)
@@ -1740,6 +1786,10 @@ func syncGreeterColorSource(homeDir, cacheDir string, state greeterThemeSyncStat
17401786
}
17411787

17421788
func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
1789+
if err := EnsureVoidLogindGreetdCommand(logFunc, sudoPassword); err != nil {
1790+
return err
1791+
}
1792+
17431793
homeDir, err := os.UserHomeDir()
17441794
if err != nil {
17451795
return fmt.Errorf("failed to get user home directory: %w", err)
@@ -2271,13 +2321,7 @@ vt = 1
22712321
return fmt.Errorf("failed to read greetd config: %w", err)
22722322
}
22732323

2274-
wrapperCmd := resolveGreeterWrapperPath()
2275-
2276-
compositorLower := strings.ToLower(compositor)
2277-
commandValue := fmt.Sprintf("%s --command %s --cache-dir %s", wrapperCmd, compositorLower, GreeterCacheDir)
2278-
if dmsPath != "" {
2279-
commandValue = fmt.Sprintf("%s -p %s", commandValue, dmsPath)
2280-
}
2324+
commandValue := buildGreetdCommand(resolveGreeterWrapperPath(), compositor, dmsPath, IsVoidLinux())
22812325

22822326
commandLine := fmt.Sprintf(`command = "%s"`, commandValue)
22832327
newConfig := upsertDefaultSession(configContent, greeterUser, commandLine)
@@ -2289,6 +2333,84 @@ vt = 1
22892333
return nil
22902334
}
22912335

2336+
func buildGreetdCommand(wrapperCmd, compositor, dmsPath string, useVoidLogind bool) string {
2337+
commandValue := fmt.Sprintf("%s --command %s --cache-dir %s", wrapperCmd, strings.ToLower(compositor), GreeterCacheDir)
2338+
if dmsPath != "" {
2339+
commandValue = fmt.Sprintf("%s -p %s", commandValue, dmsPath)
2340+
}
2341+
if useVoidLogind {
2342+
commandValue = "env LIBSEAT_BACKEND=logind DMS_VOID=1 " + commandValue
2343+
}
2344+
return commandValue
2345+
}
2346+
2347+
// EnsureVoidLogindGreetdCommand migrates DMS greeter commands on Void.
2348+
func EnsureVoidLogindGreetdCommand(logFunc func(string), sudoPassword string) error {
2349+
if !IsVoidLinux() {
2350+
return nil
2351+
}
2352+
2353+
const configPath = "/etc/greetd/config.toml"
2354+
data, err := os.ReadFile(configPath)
2355+
if os.IsNotExist(err) {
2356+
return nil
2357+
}
2358+
if err != nil {
2359+
return fmt.Errorf("failed to read greetd config: %w", err)
2360+
}
2361+
2362+
configContent := string(data)
2363+
command := extractDefaultSessionCommand(configContent)
2364+
if command == "" || !strings.Contains(command, "dms-greeter") {
2365+
return nil
2366+
}
2367+
2368+
migratedCommand := voidLogindGreeterCommand(command)
2369+
if migratedCommand == command {
2370+
return nil
2371+
}
2372+
2373+
greeterUser := extractDefaultSessionUser(configContent)
2374+
if greeterUser == "" {
2375+
greeterUser = DetectGreeterUser()
2376+
}
2377+
newConfig := upsertDefaultSession(configContent, greeterUser, fmt.Sprintf(`command = "%s"`, migratedCommand))
2378+
return writeGreetdConfig(configPath, newConfig, logFunc, sudoPassword, "✓ Updated existing Void greeter to use elogind")
2379+
}
2380+
2381+
func extractDefaultSessionCommand(configContent string) string {
2382+
inDefaultSession := false
2383+
for line := range strings.SplitSeq(configContent, "\n") {
2384+
if section, ok := parseTomlSection(line); ok {
2385+
inDefaultSession = section == "default_session"
2386+
continue
2387+
}
2388+
if !inDefaultSession {
2389+
continue
2390+
}
2391+
2392+
trimmed := stripTomlComment(line)
2393+
if !strings.HasPrefix(trimmed, "command =") && !strings.HasPrefix(trimmed, "command=") {
2394+
continue
2395+
}
2396+
parts := strings.SplitN(trimmed, "=", 2)
2397+
if len(parts) != 2 {
2398+
continue
2399+
}
2400+
if command := strings.Trim(strings.TrimSpace(parts[1]), `"`); command != "" {
2401+
return command
2402+
}
2403+
}
2404+
return ""
2405+
}
2406+
2407+
func voidLogindGreeterCommand(command string) string {
2408+
if strings.Contains(command, "LIBSEAT_BACKEND=logind") && strings.Contains(command, "DMS_VOID=1") {
2409+
return command
2410+
}
2411+
return "env LIBSEAT_BACKEND=logind DMS_VOID=1 " + command
2412+
}
2413+
22922414
func stripConfigFlag(command string) string {
22932415
for _, flag := range []string{" -C ", " --config "} {
22942416
idx := strings.Index(command, flag)
@@ -2430,7 +2552,11 @@ func EnableGreetd(sudoPassword string, logFunc func(string)) error {
24302552
if !runitServiceInstalled("greetd") {
24312553
return fmt.Errorf("greetd service not found in %s; ensure greetd is installed", runitSvDir)
24322554
}
2433-
ensureRunitSeat(DetectGreeterUser(), sudoPassword, logFunc)
2555+
if IsVoidLinux() {
2556+
ensureVoidLogindGreeter(DetectGreeterUser(), sudoPassword, logFunc)
2557+
} else {
2558+
ensureRunitSeat(DetectGreeterUser(), sudoPassword, logFunc)
2559+
}
24342560
ensureGreetdPamRundir(sudoPassword, logFunc)
24352561
if err := enableRunitService("greetd", sudoPassword); err != nil {
24362562
return fmt.Errorf("failed to enable greetd: %w", err)

core/internal/greeter/installer_test.go

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,56 @@ func TestStripDesktopExecCodes(t *testing.T) {
169169
}
170170
}
171171

172+
func TestBuildGreetdCommand(t *testing.T) {
173+
t.Parallel()
174+
175+
tests := []struct {
176+
name string
177+
wrapper string
178+
compositor string
179+
dmsPath string
180+
useVoidLogind bool
181+
want string
182+
}{
183+
{
184+
name: "standard command",
185+
wrapper: "/usr/bin/dms-greeter",
186+
compositor: "Niri",
187+
want: "/usr/bin/dms-greeter --command niri --cache-dir /var/cache/dms-greeter",
188+
},
189+
{
190+
name: "void selects elogind and keeps custom DMS path",
191+
wrapper: "/usr/bin/dms-greeter",
192+
compositor: "Niri",
193+
dmsPath: "/usr/share/quickshell/dms-greeter",
194+
useVoidLogind: true,
195+
want: "env LIBSEAT_BACKEND=logind DMS_VOID=1 /usr/bin/dms-greeter --command niri --cache-dir /var/cache/dms-greeter -p /usr/share/quickshell/dms-greeter",
196+
},
197+
}
198+
199+
for _, tt := range tests {
200+
t.Run(tt.name, func(t *testing.T) {
201+
t.Parallel()
202+
if got := buildGreetdCommand(tt.wrapper, tt.compositor, tt.dmsPath, tt.useVoidLogind); got != tt.want {
203+
t.Fatalf("buildGreetdCommand() = %q, want %q", got, tt.want)
204+
}
205+
})
206+
}
207+
}
208+
209+
func TestVoidLogindGreeterCommand(t *testing.T) {
210+
t.Parallel()
211+
212+
const oldCommand = "/usr/bin/dms-greeter --command niri -C /etc/greetd/niri.kdl"
213+
const want = "env LIBSEAT_BACKEND=logind DMS_VOID=1 " + oldCommand
214+
if got := voidLogindGreeterCommand(oldCommand); got != want {
215+
t.Fatalf("voidLogindGreeterCommand() = %q, want %q", got, want)
216+
}
217+
if got := voidLogindGreeterCommand(want); got != want {
218+
t.Fatalf("voidLogindGreeterCommand() must be idempotent, got %q", got)
219+
}
220+
}
221+
172222
func TestResolveGreeterAutoLoginState(t *testing.T) {
173223
t.Parallel()
174224

0 commit comments

Comments
 (0)