@@ -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\n chmod 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+
118164func 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
17421788func 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+
22922414func 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 )
0 commit comments