1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-04 21:02:06 -04:00

refactor(greeter): Update auth flows and add configurable opts

- Finally fix debug info logs before dms greeter loads
- prevent greeter/lockscreen auth stalls with timeout recovery and unlock-state sync
This commit is contained in:
purian23
2026-03-04 14:17:56 -05:00
committed by bbedward
parent 2ff42eba41
commit 73c75fcc2c
13 changed files with 1032 additions and 281 deletions

View File

@@ -486,22 +486,22 @@ func enableGreeter() error {
configPath := "/etc/greetd/config.toml"
if _, err := os.Stat(configPath); os.IsNotExist(err) {
return fmt.Errorf("greetd config not found at %s\nPlease install greetd first", configPath)
} else if err != nil {
return fmt.Errorf("failed to access greetd config at %s: %w", configPath, err)
}
data, err := os.ReadFile(configPath)
if err != nil {
return fmt.Errorf("failed to read greetd config: %w", err)
}
configContent := string(data)
if greeter.IsGreeterPackaged() && greeter.HasLegacyLocalGreeterWrapper() {
return fmt.Errorf("legacy manual wrapper detected at /usr/local/bin/dms-greeter; remove it before using packaged dms-greeter: sudo rm -f /usr/local/bin/dms-greeter")
}
configAlreadyCorrect := strings.Contains(configContent, "dms-greeter")
configAlreadyCorrect := isGreeterEnabled()
configuredCompositor := detectConfiguredCompositor()
if configAlreadyCorrect {
fmt.Println("✓ Greeter is already configured with dms-greeter")
if configuredCompositor != "" {
fmt.Printf("✓ Configured compositor: %s\n", configuredCompositor)
}
if err := ensureGraphicalTarget(); err != nil {
return err
@@ -548,68 +548,21 @@ func enableGreeter() error {
fmt.Printf("✓ Selected compositor: %s\n", selectedCompositor)
}
backupPath := configPath + ".backup"
backupCmd := exec.Command("sudo", "cp", configPath, backupPath)
if err := backupCmd.Run(); err != nil {
return fmt.Errorf("failed to backup config: %w", err)
}
fmt.Printf("✓ Backed up config to %s\n", backupPath)
lines := strings.Split(configContent, "\n")
var newLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "command =") && !strings.HasPrefix(trimmed, "command=") {
newLines = append(newLines, line)
greeterPathForConfig := ""
if !greeter.IsGreeterPackaged() {
dmsPath, err := greeter.DetectDMSPath()
if err != nil {
return fmt.Errorf("failed to detect DMS path for manual greeter configuration: %w", err)
}
greeterPathForConfig = dmsPath
}
wrapperCmd, err := findCommandPath("dms-greeter")
if err != nil {
return fmt.Errorf("dms-greeter not found in PATH. Please ensure it is installed and accessible")
logFunc := func(msg string) {
fmt.Println(msg)
}
compositorLower := strings.ToLower(selectedCompositor)
commandLine := fmt.Sprintf(`command = "%s --command %s"`, wrapperCmd, compositorLower)
var finalLines []string
inDefaultSession := false
commandAdded := false
for _, line := range newLines {
finalLines = append(finalLines, line)
trimmed := strings.TrimSpace(line)
if trimmed == "[default_session]" {
inDefaultSession = true
}
if inDefaultSession && !commandAdded {
if strings.HasPrefix(trimmed, "user =") || strings.HasPrefix(trimmed, "user=") {
finalLines = append(finalLines, commandLine)
commandAdded = true
}
}
if err := greeter.ConfigureGreetd(greeterPathForConfig, selectedCompositor, logFunc, ""); err != nil {
return fmt.Errorf("failed to configure greetd: %w", err)
}
if !commandAdded {
finalLines = append(finalLines, commandLine)
}
newConfig := strings.Join(finalLines, "\n")
tmpFile := "/tmp/greetd-config.toml"
if err := os.WriteFile(tmpFile, []byte(newConfig), 0o644); err != nil {
return fmt.Errorf("failed to write temp config: %w", err)
}
moveCmd := exec.Command("sudo", "mv", tmpFile, configPath)
if err := moveCmd.Run(); err != nil {
return fmt.Errorf("failed to update config: %w", err)
}
fmt.Printf("✓ Updated greetd configuration to use %s\n", selectedCompositor)
if err := ensureGraphicalTarget(); err != nil {
return err
}
@@ -631,32 +584,69 @@ func enableGreeter() error {
}
func isGreeterEnabled() bool {
data, err := os.ReadFile("/etc/greetd/config.toml")
if err != nil {
return false
}
return strings.Contains(string(data), "dms-greeter")
command := readDefaultSessionCommand("/etc/greetd/config.toml")
return command != "" && strings.Contains(command, "dms-greeter")
}
func detectConfiguredCompositor() string {
data, err := os.ReadFile("/etc/greetd/config.toml")
command := strings.ToLower(readDefaultSessionCommand("/etc/greetd/config.toml"))
switch {
case strings.Contains(command, "--command niri"):
return "niri"
case strings.Contains(command, "--command hyprland"):
return "hyprland"
case strings.Contains(command, "--command sway"):
return "sway"
}
return ""
}
func stripTomlComment(line string) string {
trimmed := strings.TrimSpace(line)
if idx := strings.Index(trimmed, "#"); idx >= 0 {
return strings.TrimSpace(trimmed[:idx])
}
return trimmed
}
func parseTomlSection(line string) (string, bool) {
trimmed := stripTomlComment(line)
if len(trimmed) < 3 || !strings.HasPrefix(trimmed, "[") || !strings.HasSuffix(trimmed, "]") {
return "", false
}
return strings.TrimSpace(trimmed[1 : len(trimmed)-1]), true
}
func readDefaultSessionCommand(configPath string) string {
data, err := os.ReadFile(configPath)
if err != nil {
return ""
}
for _, line := range strings.Split(string(data), "\n") {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "command") || !strings.Contains(trimmed, "dms-greeter") {
inDefaultSession := false
for line := range strings.SplitSeq(string(data), "\n") {
if section, ok := parseTomlSection(line); ok {
inDefaultSession = section == "default_session"
continue
}
switch {
case strings.Contains(trimmed, "--command niri"):
return "niri"
case strings.Contains(trimmed, "--command hyprland"):
return "hyprland"
case strings.Contains(trimmed, "--command sway"):
return "sway"
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
}
command := strings.Trim(strings.TrimSpace(parts[1]), `"`)
if command != "" {
return command
}
}
@@ -742,30 +732,21 @@ func checkGreeterStatus() error {
configPath := "/etc/greetd/config.toml"
fmt.Println("Greeter Configuration:")
if data, err := os.ReadFile(configPath); err == nil {
configContent := string(data)
if strings.Contains(configContent, "dms-greeter") {
lines := strings.SplitSeq(configContent, "\n")
for line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "command =") || strings.HasPrefix(trimmed, "command=") {
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) == 2 {
command := strings.Trim(strings.TrimSpace(parts[1]), `"`)
fmt.Println(" ✓ Greeter is enabled")
if _, err := os.ReadFile(configPath); err == nil {
command := readDefaultSessionCommand(configPath)
if command != "" && strings.Contains(command, "dms-greeter") {
fmt.Println(" ✓ Greeter is enabled")
if strings.Contains(command, "--command niri") {
fmt.Println(" Compositor: niri")
} else if strings.Contains(command, "--command hyprland") {
fmt.Println(" Compositor: Hyprland")
} else if strings.Contains(command, "--command sway") {
fmt.Println(" Compositor: sway")
} else {
fmt.Println(" Compositor: unknown")
}
}
break
}
compositor := detectConfiguredCompositor()
switch compositor {
case "niri":
fmt.Println(" Compositor: niri")
case "hyprland":
fmt.Println(" Compositor: Hyprland")
case "sway":
fmt.Println(" Compositor: sway")
default:
fmt.Println(" Compositor: unknown")
}
} else {
fmt.Println(" ✗ Greeter is NOT enabled")

View File

@@ -7,14 +7,6 @@ import (
"strings"
)
func findCommandPath(cmd string) (string, error) {
path, err := exec.LookPath(cmd)
if err != nil {
return "", fmt.Errorf("command '%s' not found in PATH", cmd)
}
return path, nil
}
func isArchPackageInstalled(packageName string) bool {
cmd := exec.Command("pacman", "-Q", packageName)
err := cmd.Run()

View File

@@ -36,6 +36,184 @@ func DetectGreeterGroup() string {
return "greeter"
}
func hasPasswdUser(passwdData, user string) bool {
prefix := user + ":"
for line := range strings.SplitSeq(passwdData, "\n") {
if strings.HasPrefix(line, prefix) {
return true
}
}
return false
}
func findPasswdUser(passwdData string, candidates ...string) (string, bool) {
for _, candidate := range candidates {
if hasPasswdUser(passwdData, candidate) {
return candidate, true
}
}
return "", false
}
func stripTomlComment(line string) string {
trimmed := strings.TrimSpace(line)
if idx := strings.Index(trimmed, "#"); idx >= 0 {
return strings.TrimSpace(trimmed[:idx])
}
return trimmed
}
func parseTomlSection(line string) (string, bool) {
trimmed := stripTomlComment(line)
if len(trimmed) < 3 || !strings.HasPrefix(trimmed, "[") || !strings.HasSuffix(trimmed, "]") {
return "", false
}
return strings.TrimSpace(trimmed[1 : len(trimmed)-1]), true
}
func extractDefaultSessionUser(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, "user =") && !strings.HasPrefix(trimmed, "user=") {
continue
}
parts := strings.SplitN(trimmed, "=", 2)
if len(parts) != 2 {
continue
}
user := strings.Trim(strings.TrimSpace(parts[1]), `"`)
if user != "" {
return user
}
}
return ""
}
func upsertDefaultSession(configContent, greeterUser, command string) string {
lines := strings.Split(configContent, "\n")
var out []string
inDefaultSession := false
foundDefaultSession := false
defaultSessionUserSet := false
defaultSessionCommandSet := false
appendDefaultSessionFields := func() {
if !defaultSessionUserSet {
out = append(out, fmt.Sprintf(`user = "%s"`, greeterUser))
}
if !defaultSessionCommandSet {
out = append(out, command)
}
}
for _, line := range lines {
if section, ok := parseTomlSection(line); ok {
if inDefaultSession {
appendDefaultSessionFields()
}
inDefaultSession = section == "default_session"
if inDefaultSession {
foundDefaultSession = true
defaultSessionUserSet = false
defaultSessionCommandSet = false
}
out = append(out, line)
continue
}
if inDefaultSession {
trimmed := stripTomlComment(line)
if strings.HasPrefix(trimmed, "user =") || strings.HasPrefix(trimmed, "user=") {
out = append(out, fmt.Sprintf(`user = "%s"`, greeterUser))
defaultSessionUserSet = true
continue
}
if strings.HasPrefix(trimmed, "command =") || strings.HasPrefix(trimmed, "command=") {
if !defaultSessionCommandSet {
out = append(out, command)
defaultSessionCommandSet = true
}
continue
}
}
out = append(out, line)
}
if inDefaultSession {
appendDefaultSessionFields()
}
if !foundDefaultSession {
if len(out) > 0 && strings.TrimSpace(out[len(out)-1]) != "" {
out = append(out, "")
}
out = append(out, "[default_session]")
out = append(out, fmt.Sprintf(`user = "%s"`, greeterUser))
out = append(out, command)
}
return strings.Join(out, "\n")
}
func DetectGreeterUser() string {
passwdData, err := os.ReadFile("/etc/passwd")
if err == nil {
passwdContent := string(passwdData)
if configData, cfgErr := os.ReadFile("/etc/greetd/config.toml"); cfgErr == nil {
if configured := extractDefaultSessionUser(string(configData)); configured != "" && hasPasswdUser(passwdContent, configured) {
return configured
}
}
if user, found := findPasswdUser(passwdContent, "greeter", "_greeter", "greetd"); found {
return user
}
} else {
fmt.Fprintln(os.Stderr, "⚠ Warning: could not read /etc/passwd, defaulting greeter user to 'greeter'")
}
if configData, cfgErr := os.ReadFile("/etc/greetd/config.toml"); cfgErr == nil {
if configured := extractDefaultSessionUser(string(configData)); configured != "" {
return configured
}
}
fmt.Fprintln(os.Stderr, "⚠ Warning: no greeter user found, defaulting to 'greeter'")
return "greeter"
}
func resolveGreeterWrapperPath() string {
if path, err := exec.LookPath("dms-greeter"); err == nil {
return path
}
for _, candidate := range []string{"/usr/local/bin/dms-greeter", "/usr/bin/dms-greeter"} {
if _, err := os.Stat(candidate); err == nil {
return candidate
}
}
return "dms-greeter"
}
func DetectCompositors() []string {
var compositors []string
@@ -289,9 +467,13 @@ func TryInstallGreeterPackage(logFunc func(string), sudoPassword string) bool {
// CopyGreeterFiles installs the dms-greeter wrapper and sets up cache directory
func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPassword string) error {
if utils.CommandExists("dms-greeter") {
logFunc("✓ dms-greeter wrapper already installed")
if IsGreeterPackaged() {
logFunc("✓ dms-greeter package already installed")
} else {
if dmsPath == "" {
return fmt.Errorf("dms path is required for manual dms-greeter wrapper installs")
}
assetsDir := filepath.Join(dmsPath, "Modules", "Greetd", "assets")
wrapperSrc := filepath.Join(assetsDir, "dms-greeter")
@@ -300,10 +482,14 @@ func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPass
}
wrapperDst := "/usr/local/bin/dms-greeter"
action := "Installed"
if info, err := os.Stat(wrapperDst); err == nil && !info.IsDir() {
action = "Updated"
}
if err := runSudoCmd(sudoPassword, "cp", wrapperSrc, wrapperDst); err != nil {
return fmt.Errorf("failed to copy dms-greeter wrapper: %w", err)
}
logFunc(fmt.Sprintf("✓ Installed dms-greeter wrapper to %s", wrapperDst))
logFunc(fmt.Sprintf("✓ %s dms-greeter wrapper at %s", action, wrapperDst))
if err := runSudoCmd(sudoPassword, "chmod", "+x", wrapperDst); err != nil {
return fmt.Errorf("failed to make wrapper executable: %w", err)
@@ -954,92 +1140,61 @@ func ConfigureGreetd(dmsPath, compositor string, logFunc func(string), sudoPassw
return fmt.Errorf("failed to backup config: %w", err)
}
logFunc(fmt.Sprintf("✓ Backed up existing config to %s", backupPath))
} else if err != nil && !os.IsNotExist(err) {
return fmt.Errorf("failed to access %s: %w", configPath, err)
}
greeterUser := DetectGreeterGroup()
greeterUser := DetectGreeterUser()
var configContent string
if data, err := os.ReadFile(configPath); err == nil {
configContent = string(data)
} else {
configContent = fmt.Sprintf(`[terminal]
} else if os.IsNotExist(err) {
configContent = `[terminal]
vt = 1
[default_session]
user = "%s"
`, greeterUser)
`
} else {
return fmt.Errorf("failed to read greetd config: %w", err)
}
lines := strings.Split(configContent, "\n")
var newLines []string
for _, line := range lines {
trimmed := strings.TrimSpace(line)
if !strings.HasPrefix(trimmed, "command =") && !strings.HasPrefix(trimmed, "command=") {
if strings.HasPrefix(trimmed, "user =") || strings.HasPrefix(trimmed, "user=") {
newLines = append(newLines, fmt.Sprintf(`user = "%s"`, greeterUser))
} else {
newLines = append(newLines, line)
}
}
}
// If dmsPath is empty (packaged greeter), omit -p; wrapper finds /usr/share/quickshell/dms-greeter
wrapperCmd := "dms-greeter"
if !utils.CommandExists("dms-greeter") {
wrapperCmd = "/usr/local/bin/dms-greeter"
}
wrapperCmd := resolveGreeterWrapperPath()
// Build command based on compositor and dms path
// When dmsPath is empty (packaged greeter), omit -p; wrapper finds /usr/share/quickshell/dms-greeter
compositorLower := strings.ToLower(compositor)
var command string
if dmsPath == "" {
command = fmt.Sprintf(`command = "%s --command %s"`, wrapperCmd, compositorLower)
} else {
command = fmt.Sprintf(`command = "%s --command %s -p %s"`, wrapperCmd, compositorLower, dmsPath)
}
var finalLines []string
inDefaultSession := false
commandAdded := false
for _, line := range newLines {
finalLines = append(finalLines, line)
trimmed := strings.TrimSpace(line)
if trimmed == "[default_session]" {
inDefaultSession = true
}
if inDefaultSession && !commandAdded && trimmed != "" && !strings.HasPrefix(trimmed, "[") {
if !strings.HasPrefix(trimmed, "#") && !strings.HasPrefix(trimmed, "user") {
finalLines = append(finalLines, command)
commandAdded = true
}
}
}
if !commandAdded {
finalLines = append(finalLines, command)
}
newConfig := strings.Join(finalLines, "\n")
tmpFile := "/tmp/greetd-config.toml"
if err := os.WriteFile(tmpFile, []byte(newConfig), 0o644); err != nil {
return fmt.Errorf("failed to write temp config: %w", err)
}
if err := runSudoCmd(sudoPassword, "mv", tmpFile, configPath); err != nil {
return fmt.Errorf("failed to move config to /etc/greetd: %w", err)
}
cmdDesc := fmt.Sprintf("%s --command %s", wrapperCmd, compositorLower)
commandValue := fmt.Sprintf("%s --command %s", wrapperCmd, compositorLower)
if dmsPath != "" {
cmdDesc = fmt.Sprintf("%s -p %s", cmdDesc, dmsPath)
commandValue = fmt.Sprintf("%s -p %s", commandValue, dmsPath)
}
logFunc(fmt.Sprintf("✓ Updated greetd configuration (user: %s, command: %s)", greeterUser, cmdDesc))
commandLine := fmt.Sprintf(`command = "%s"`, commandValue)
newConfig := upsertDefaultSession(configContent, greeterUser, commandLine)
tmpFile, err := os.CreateTemp("", "greetd-config-*.toml")
if err != nil {
return fmt.Errorf("failed to create temp greetd config: %w", err)
}
defer os.Remove(tmpFile.Name())
if _, err := tmpFile.WriteString(newConfig); err != nil {
_ = tmpFile.Close()
return fmt.Errorf("failed to write temp greetd config: %w", err)
}
if err := tmpFile.Close(); err != nil {
return fmt.Errorf("failed to close temp greetd config: %w", err)
}
if err := runSudoCmd(sudoPassword, "mkdir", "-p", "/etc/greetd"); err != nil {
return fmt.Errorf("failed to create /etc/greetd: %w", err)
}
if err := runSudoCmd(sudoPassword, "install", "-o", "root", "-g", "root", "-m", "0644", tmpFile.Name(), configPath); err != nil {
return fmt.Errorf("failed to install greetd config: %w", err)
}
logFunc(fmt.Sprintf("✓ Updated greetd configuration (user: %s, command: %s)", greeterUser, commandValue))
return nil
}