mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-08-02 03:28:28 -04:00
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
(cherry picked from commit 71ab752e1b)
This commit is contained in:
@@ -1081,8 +1081,11 @@ func ensureGreetdEnabled() error {
|
||||
if !runitServiceInstalled("greetd") {
|
||||
return fmt.Errorf("greetd service not found in %s. Please install greetd first", runitSvDir)
|
||||
}
|
||||
// Seat + runtime-dir setup that logind handles automatically on systemd.
|
||||
ensureRunitSeat("_greeter")
|
||||
if greeter.IsVoidLinux() {
|
||||
ensureVoidLogindGreeter("_greeter")
|
||||
} else {
|
||||
ensureRunitSeat("_greeter")
|
||||
}
|
||||
ensureGreetdPamRundir()
|
||||
if err := enableRunitService("greetd"); err != nil {
|
||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||
@@ -1261,6 +1264,9 @@ func enableGreeter(nonInteractive bool) error {
|
||||
if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
|
||||
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)
|
||||
}
|
||||
if err := greeter.EnsureVoidLogindGreetdCommand(logFunc, ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := ensureGraphicalTarget(); err != nil {
|
||||
return err
|
||||
@@ -1475,12 +1481,23 @@ func extractGreeterWrapperFromCommand(command string) string {
|
||||
if len(tokens) == 0 {
|
||||
return ""
|
||||
}
|
||||
wrapper := strings.Trim(tokens[0], "\"")
|
||||
wrapperIndex := 0
|
||||
if filepath.Base(strings.Trim(tokens[0], "\"")) == "env" {
|
||||
wrapperIndex = 1
|
||||
for wrapperIndex < len(tokens) && strings.Contains(tokens[wrapperIndex], "=") {
|
||||
wrapperIndex++
|
||||
}
|
||||
}
|
||||
if wrapperIndex >= len(tokens) {
|
||||
return ""
|
||||
}
|
||||
|
||||
wrapper := strings.Trim(tokens[wrapperIndex], "\"")
|
||||
if wrapper == "" {
|
||||
return ""
|
||||
}
|
||||
if len(tokens) > 1 {
|
||||
next := strings.Trim(tokens[1], "\"")
|
||||
if wrapperIndex+1 < len(tokens) {
|
||||
next := strings.Trim(tokens[wrapperIndex+1], "\"")
|
||||
if next != "" && (filepath.Base(wrapper) == "bash" || filepath.Base(wrapper) == "sh") && strings.Contains(filepath.Base(next), "dms-greeter") {
|
||||
return fmt.Sprintf("%s (script: %s)", wrapper, next)
|
||||
}
|
||||
|
||||
@@ -101,6 +101,13 @@ func TestGreeterStatusStateDirHonorsExplicitOverrideOnNixOS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestExtractGreeterWrapperFromEnvCommand(t *testing.T) {
|
||||
command := "env LIBSEAT_BACKEND=logind DMS_VOID=1 /usr/bin/dms-greeter --command niri"
|
||||
if got := extractGreeterWrapperFromCommand(command); got != "/usr/bin/dms-greeter" {
|
||||
t.Fatalf("extractGreeterWrapperFromCommand() = %q, want %q", got, "/usr/bin/dms-greeter")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectNixOSGreeterMutationBlocksImperativeCommands(t *testing.T) {
|
||||
origGreeterIsNixOSFn := greeterIsNixOSFn
|
||||
greeterIsNixOSFn = func() bool { return true }
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||
)
|
||||
|
||||
@@ -83,9 +84,31 @@ func ensureRunitSeat(greeterUser string) {
|
||||
}
|
||||
}
|
||||
|
||||
// ensureGreetdPamRundir adds pam_rundir to the greetd PAM stack so the post-login
|
||||
// session gets an XDG_RUNTIME_DIR on systems without logind (Void with seatd).
|
||||
// Appended outside DMS's managed auth block so it survives `dms greeter sync`.
|
||||
// ensureVoidLogindGreeter configures the elogind-backed greeter on Void.
|
||||
func ensureVoidLogindGreeter(greeterUser string) {
|
||||
for _, service := range []string{"dbus", "elogind"} {
|
||||
if err := enableRunitService(service); err != nil {
|
||||
fmt.Printf(" ⚠ could not enable %s: %v\n", service, err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s enabled\n", service)
|
||||
}
|
||||
}
|
||||
greeter.EnsureVoidGreetdRunScript(func(msg string) { fmt.Println(" " + msg) }, "")
|
||||
if runitServiceEnabled("seatd") {
|
||||
if err := disableRunitService("seatd"); err != nil {
|
||||
fmt.Printf(" ⚠ could not disable seatd: %v\n", err)
|
||||
} else {
|
||||
fmt.Println(" ✓ seatd disabled (elogind manages the seat)")
|
||||
}
|
||||
}
|
||||
if err := privesc.Run(context.Background(), "", "usermod", "-aG", "video,input", greeterUser); err != nil {
|
||||
fmt.Printf(" ⚠ could not add %s to video/input groups: %v\n", greeterUser, err)
|
||||
} else {
|
||||
fmt.Printf(" ✓ %s added to video/input groups (elogind manages the seat)\n", greeterUser)
|
||||
}
|
||||
}
|
||||
|
||||
// ensureGreetdPamRundir provides XDG_RUNTIME_DIR to runit greeter sessions.
|
||||
func ensureGreetdPamRundir() {
|
||||
const pamPath = "/etc/pam.d/greetd"
|
||||
data, err := os.ReadFile(pamPath)
|
||||
|
||||
@@ -72,6 +72,7 @@ func (v *VoidDistribution) DetectDependenciesWithTerminal(ctx context.Context, w
|
||||
dependencies = append(dependencies, v.detectAccountsService())
|
||||
dependencies = append(dependencies, v.detectDBus())
|
||||
dependencies = append(dependencies, v.detectElogind())
|
||||
dependencies = append(dependencies, v.detectMesaDri())
|
||||
|
||||
if wm == deps.WindowManagerHyprland {
|
||||
dependencies = append(dependencies, v.detectHyprlandTools()...)
|
||||
@@ -142,6 +143,10 @@ func (v *VoidDistribution) detectElogind() deps.Dependency {
|
||||
return v.detectPackage("elogind", "loginctl/logind provider for power management and session tracking", v.packageInstalled("elogind") || v.commandExists("loginctl"))
|
||||
}
|
||||
|
||||
func (v *VoidDistribution) detectMesaDri() deps.Dependency {
|
||||
return v.detectPackage("mesa-dri", "Mesa DRI/EGL drivers (GPU rendering; compositors find no outputs without it)", v.packageInstalled("mesa-dri"))
|
||||
}
|
||||
|
||||
func (v *VoidDistribution) detectXwaylandSatellite() deps.Dependency {
|
||||
return v.detectPackage("xwayland-satellite", "Xwayland support", v.packageInstalled("xwayland-satellite"))
|
||||
}
|
||||
@@ -172,6 +177,7 @@ func (v *VoidDistribution) GetPackageMappingWithVariants(wm deps.WindowManager,
|
||||
"accountsservice": {Name: "accountsservice", Repository: RepoTypeSystem},
|
||||
"dbus": {Name: "dbus", Repository: RepoTypeSystem},
|
||||
"elogind": {Name: "elogind", Repository: RepoTypeSystem},
|
||||
"mesa-dri": {Name: "mesa-dri", Repository: RepoTypeSystem},
|
||||
|
||||
"quickshell": {Name: "quickshell", Repository: RepoTypeSystem},
|
||||
"matugen": {Name: "matugen", Repository: RepoTypeSystem},
|
||||
@@ -305,6 +311,7 @@ func (v *VoidDistribution) ensureSessionServices(ctx context.Context, sudoPasswo
|
||||
return nil
|
||||
}
|
||||
|
||||
// D-Bus activation alone starts elogind without its wrapper mounts; the runit service is required.
|
||||
for _, service := range []string{"dbus", "elogind"} {
|
||||
if !v.runitServiceInstalled(service) {
|
||||
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))
|
||||
|
||||
@@ -115,6 +115,52 @@ func ensureRunitSeat(greeterUser, sudoPassword string, logFunc func(string)) {
|
||||
}
|
||||
}
|
||||
|
||||
// VoidGreetdRunScript orders greetd after dbus/elogind so the first greeter session can't race elogind's runtime-dir setup.
|
||||
const VoidGreetdRunScript = `#!/bin/sh
|
||||
sv check dbus >/dev/null || exit 1
|
||||
sv check elogind >/dev/null || exit 1
|
||||
exec greetd
|
||||
`
|
||||
|
||||
// EnsureVoidGreetdRunScript rewrites /etc/sv/greetd/run with dbus/elogind ordering (greetd updates restore stock; enable re-asserts).
|
||||
func EnsureVoidGreetdRunScript(logFunc func(string), sudoPassword string) {
|
||||
const runPath = "/etc/sv/greetd/run"
|
||||
if data, err := os.ReadFile(runPath); err == nil && strings.Contains(string(data), "sv check elogind") {
|
||||
logFunc("✓ greetd run script already waits for elogind")
|
||||
return
|
||||
}
|
||||
script := fmt.Sprintf("cat > %s <<'EOF'\n%sEOF\nchmod 755 %s", runPath, VoidGreetdRunScript, runPath)
|
||||
if err := privesc.Run(context.Background(), sudoPassword, "sh", "-c", script); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ could not update %s: %v", runPath, err))
|
||||
return
|
||||
}
|
||||
logFunc("✓ greetd run script now waits for dbus/elogind")
|
||||
}
|
||||
|
||||
// ensureVoidLogindGreeter configures the elogind-backed greeter on Void.
|
||||
func ensureVoidLogindGreeter(greeterUser, sudoPassword string, logFunc func(string)) {
|
||||
for _, service := range []string{"dbus", "elogind"} {
|
||||
if err := enableRunitService(service, sudoPassword); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ could not enable %s: %v", service, err))
|
||||
} else {
|
||||
logFunc(fmt.Sprintf("✓ %s enabled", service))
|
||||
}
|
||||
}
|
||||
EnsureVoidGreetdRunScript(logFunc, sudoPassword)
|
||||
if runitServiceEnabled("seatd") {
|
||||
if err := disableRunitService("seatd", sudoPassword); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ could not disable seatd: %v", err))
|
||||
} else {
|
||||
logFunc("✓ seatd disabled (elogind manages the seat)")
|
||||
}
|
||||
}
|
||||
if err := privesc.Run(context.Background(), sudoPassword, "usermod", "-aG", "video,input", greeterUser); err != nil {
|
||||
logFunc(fmt.Sprintf("⚠ could not add %s to video/input groups: %v", greeterUser, err))
|
||||
} else {
|
||||
logFunc(fmt.Sprintf("✓ %s added to video/input groups (elogind manages the seat)", greeterUser))
|
||||
}
|
||||
}
|
||||
|
||||
func ensureGreetdPamRundir(sudoPassword string, logFunc func(string)) {
|
||||
const pamPath = "/etc/pam.d/greetd"
|
||||
data, err := os.ReadFile(pamPath)
|
||||
@@ -1740,6 +1786,10 @@ func syncGreeterColorSource(homeDir, cacheDir string, state greeterThemeSyncStat
|
||||
}
|
||||
|
||||
func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
|
||||
if err := EnsureVoidLogindGreetdCommand(logFunc, sudoPassword); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
homeDir, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get user home directory: %w", err)
|
||||
@@ -2271,13 +2321,7 @@ vt = 1
|
||||
return fmt.Errorf("failed to read greetd config: %w", err)
|
||||
}
|
||||
|
||||
wrapperCmd := resolveGreeterWrapperPath()
|
||||
|
||||
compositorLower := strings.ToLower(compositor)
|
||||
commandValue := fmt.Sprintf("%s --command %s --cache-dir %s", wrapperCmd, compositorLower, GreeterCacheDir)
|
||||
if dmsPath != "" {
|
||||
commandValue = fmt.Sprintf("%s -p %s", commandValue, dmsPath)
|
||||
}
|
||||
commandValue := buildGreetdCommand(resolveGreeterWrapperPath(), compositor, dmsPath, IsVoidLinux())
|
||||
|
||||
commandLine := fmt.Sprintf(`command = "%s"`, commandValue)
|
||||
newConfig := upsertDefaultSession(configContent, greeterUser, commandLine)
|
||||
@@ -2289,6 +2333,84 @@ vt = 1
|
||||
return nil
|
||||
}
|
||||
|
||||
func buildGreetdCommand(wrapperCmd, compositor, dmsPath string, useVoidLogind bool) string {
|
||||
commandValue := fmt.Sprintf("%s --command %s --cache-dir %s", wrapperCmd, strings.ToLower(compositor), GreeterCacheDir)
|
||||
if dmsPath != "" {
|
||||
commandValue = fmt.Sprintf("%s -p %s", commandValue, dmsPath)
|
||||
}
|
||||
if useVoidLogind {
|
||||
commandValue = "env LIBSEAT_BACKEND=logind DMS_VOID=1 " + commandValue
|
||||
}
|
||||
return commandValue
|
||||
}
|
||||
|
||||
// EnsureVoidLogindGreetdCommand migrates DMS greeter commands on Void.
|
||||
func EnsureVoidLogindGreetdCommand(logFunc func(string), sudoPassword string) error {
|
||||
if !IsVoidLinux() {
|
||||
return nil
|
||||
}
|
||||
|
||||
const configPath = "/etc/greetd/config.toml"
|
||||
data, err := os.ReadFile(configPath)
|
||||
if os.IsNotExist(err) {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read greetd config: %w", err)
|
||||
}
|
||||
|
||||
configContent := string(data)
|
||||
command := extractDefaultSessionCommand(configContent)
|
||||
if command == "" || !strings.Contains(command, "dms-greeter") {
|
||||
return nil
|
||||
}
|
||||
|
||||
migratedCommand := voidLogindGreeterCommand(command)
|
||||
if migratedCommand == command {
|
||||
return nil
|
||||
}
|
||||
|
||||
greeterUser := extractDefaultSessionUser(configContent)
|
||||
if greeterUser == "" {
|
||||
greeterUser = DetectGreeterUser()
|
||||
}
|
||||
newConfig := upsertDefaultSession(configContent, greeterUser, fmt.Sprintf(`command = "%s"`, migratedCommand))
|
||||
return writeGreetdConfig(configPath, newConfig, logFunc, sudoPassword, "✓ Updated existing Void greeter to use elogind")
|
||||
}
|
||||
|
||||
func extractDefaultSessionCommand(configContent string) string {
|
||||
inDefaultSession := false
|
||||
for line := range strings.SplitSeq(configContent, "\n") {
|
||||
if section, ok := parseTomlSection(line); ok {
|
||||
inDefaultSession = section == "default_session"
|
||||
continue
|
||||
}
|
||||
if !inDefaultSession {
|
||||
continue
|
||||
}
|
||||
|
||||
trimmed := stripTomlComment(line)
|
||||
if !strings.HasPrefix(trimmed, "command =") && !strings.HasPrefix(trimmed, "command=") {
|
||||
continue
|
||||
}
|
||||
parts := strings.SplitN(trimmed, "=", 2)
|
||||
if len(parts) != 2 {
|
||||
continue
|
||||
}
|
||||
if command := strings.Trim(strings.TrimSpace(parts[1]), `"`); command != "" {
|
||||
return command
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func voidLogindGreeterCommand(command string) string {
|
||||
if strings.Contains(command, "LIBSEAT_BACKEND=logind") && strings.Contains(command, "DMS_VOID=1") {
|
||||
return command
|
||||
}
|
||||
return "env LIBSEAT_BACKEND=logind DMS_VOID=1 " + command
|
||||
}
|
||||
|
||||
func stripConfigFlag(command string) string {
|
||||
for _, flag := range []string{" -C ", " --config "} {
|
||||
idx := strings.Index(command, flag)
|
||||
@@ -2430,7 +2552,11 @@ func EnableGreetd(sudoPassword string, logFunc func(string)) error {
|
||||
if !runitServiceInstalled("greetd") {
|
||||
return fmt.Errorf("greetd service not found in %s; ensure greetd is installed", runitSvDir)
|
||||
}
|
||||
ensureRunitSeat(DetectGreeterUser(), sudoPassword, logFunc)
|
||||
if IsVoidLinux() {
|
||||
ensureVoidLogindGreeter(DetectGreeterUser(), sudoPassword, logFunc)
|
||||
} else {
|
||||
ensureRunitSeat(DetectGreeterUser(), sudoPassword, logFunc)
|
||||
}
|
||||
ensureGreetdPamRundir(sudoPassword, logFunc)
|
||||
if err := enableRunitService("greetd", sudoPassword); err != nil {
|
||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||
|
||||
@@ -169,6 +169,56 @@ func TestStripDesktopExecCodes(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildGreetdCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
wrapper string
|
||||
compositor string
|
||||
dmsPath string
|
||||
useVoidLogind bool
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "standard command",
|
||||
wrapper: "/usr/bin/dms-greeter",
|
||||
compositor: "Niri",
|
||||
want: "/usr/bin/dms-greeter --command niri --cache-dir /var/cache/dms-greeter",
|
||||
},
|
||||
{
|
||||
name: "void selects elogind and keeps custom DMS path",
|
||||
wrapper: "/usr/bin/dms-greeter",
|
||||
compositor: "Niri",
|
||||
dmsPath: "/usr/share/quickshell/dms-greeter",
|
||||
useVoidLogind: true,
|
||||
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",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
if got := buildGreetdCommand(tt.wrapper, tt.compositor, tt.dmsPath, tt.useVoidLogind); got != tt.want {
|
||||
t.Fatalf("buildGreetdCommand() = %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestVoidLogindGreeterCommand(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const oldCommand = "/usr/bin/dms-greeter --command niri -C /etc/greetd/niri.kdl"
|
||||
const want = "env LIBSEAT_BACKEND=logind DMS_VOID=1 " + oldCommand
|
||||
if got := voidLogindGreeterCommand(oldCommand); got != want {
|
||||
t.Fatalf("voidLogindGreeterCommand() = %q, want %q", got, want)
|
||||
}
|
||||
if got := voidLogindGreeterCommand(want); got != want {
|
||||
t.Fatalf("voidLogindGreeterCommand() must be idempotent, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveGreeterAutoLoginState(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user