1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-04-04 04:42:05 -04:00

greeter: New Greeter Settings UI & Sync fixes

- Add PAM Auth via GUI
- Added new sync flags
- Refactored cache directory management & many others
- Fix for wireplumber permissions
- Fix for polkit auth w/icon
- Add pam_fprintd timeout=5 to prevent 30s auth blocks when using password
This commit is contained in:
purian23
2026-03-05 23:04:59 -05:00
parent 32d16d0673
commit fe5bd42e25
18 changed files with 1814 additions and 132 deletions

View File

@@ -39,12 +39,29 @@ var greeterSyncCmd = &cobra.Command{
Short: "Sync DMS theme and settings with greeter",
Long: "Synchronize your current user's DMS theme, settings, and wallpaper configuration with the login greeter screen",
Run: func(cmd *cobra.Command, args []string) {
if err := syncGreeter(); err != nil {
yes, _ := cmd.Flags().GetBool("yes")
auth, _ := cmd.Flags().GetBool("auth")
local, _ := cmd.Flags().GetBool("local")
term, _ := cmd.Flags().GetBool("terminal")
if term {
if err := syncInTerminal(yes, auth, local); err != nil {
log.Fatalf("Error launching sync in terminal: %v", err)
}
return
}
if err := syncGreeter(yes, auth, local); err != nil {
log.Fatalf("Error syncing greeter: %v", err)
}
},
}
func init() {
greeterSyncCmd.Flags().BoolP("yes", "y", false, "Non-interactive mode: skip prompts, use defaults (for UI)")
greeterSyncCmd.Flags().BoolP("terminal", "t", false, "Run sync in a new terminal (for entering sudo password); terminal auto-closes when done")
greeterSyncCmd.Flags().BoolP("auth", "a", false, "Configure PAM for fingerprint and U2F (adds both if modules exist); overrides UI toggles")
greeterSyncCmd.Flags().BoolP("local", "l", false, "Developer mode: force greetd config to use a local DMS checkout path")
}
var greeterEnableCmd = &cobra.Command{
Use: "enable",
Short: "Enable DMS greeter in greetd config",
@@ -147,7 +164,7 @@ func installGreeter() error {
}
fmt.Println("\nSynchronizing DMS configurations...")
if err := greeter.SyncDMSConfigs(dmsPath, selectedCompositor, logFunc, ""); err != nil {
if err := greeter.SyncDMSConfigs(dmsPath, selectedCompositor, logFunc, "", false); err != nil {
return err
}
@@ -171,22 +188,88 @@ func installGreeter() error {
return nil
}
func syncGreeter() error {
fmt.Println("=== DMS Greeter Theme Sync ===")
fmt.Println()
func syncInTerminal(nonInteractive bool, forceAuth bool, local bool) error {
syncFlags := make([]string, 0, 3)
if nonInteractive {
syncFlags = append(syncFlags, "--yes")
}
if forceAuth {
syncFlags = append(syncFlags, "--auth")
}
if local {
syncFlags = append(syncFlags, "--local")
}
shellSyncCmd := "dms greeter sync"
if len(syncFlags) > 0 {
shellSyncCmd += " " + strings.Join(syncFlags, " ")
}
shellCmd := shellSyncCmd + `; echo; echo "Sync finished. Closing in 3 seconds..."; sleep 3`
terminals := []struct {
name string
args []string
}{
{"gnome-terminal", []string{"--", "bash", "-c", shellCmd}},
{"konsole", []string{"-e", "bash", "-c", shellCmd}},
{"xfce4-terminal", []string{"-e", "bash -c \"" + strings.ReplaceAll(shellCmd, `"`, `\"`) + "\""}},
{"ghostty", []string{"-e", "bash", "-c", shellCmd}},
{"wezterm", []string{"start", "--", "bash", "-c", shellCmd}},
{"alacritty", []string{"-e", "bash", "-c", shellCmd}},
{"kitty", []string{"bash", "-c", shellCmd}},
{"xterm", []string{"-e", "bash -c \"" + strings.ReplaceAll(shellCmd, `"`, `\"`) + "\""}},
}
for _, t := range terminals {
if _, err := exec.LookPath(t.name); err != nil {
continue
}
cmd := exec.Command(t.name, t.args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Start(); err != nil {
continue
}
_ = cmd.Process.Release()
return nil
}
return fmt.Errorf("no terminal emulator found (tried: gnome-terminal, konsole, xfce4-terminal, ghostty, wezterm, alacritty, kitty, xterm)")
}
func syncGreeter(nonInteractive bool, forceAuth bool, local bool) error {
if !nonInteractive {
fmt.Println("=== DMS Greeter Theme Sync ===")
fmt.Println()
}
logFunc := func(msg string) {
fmt.Println(msg)
}
fmt.Println("Detecting DMS installation...")
dmsPath, err := greeter.DetectDMSPath()
if err != nil {
return err
if !nonInteractive {
fmt.Println("Detecting DMS installation...")
}
var dmsPath string
var err error
if local {
dmsPath, err = resolveLocalDMSPath()
if err != nil {
return err
}
if !nonInteractive {
fmt.Printf("✓ Using local DMS path: %s\n", dmsPath)
}
} else {
dmsPath, err = greeter.DetectDMSPath()
if err != nil {
return err
}
if !nonInteractive {
fmt.Printf("✓ Found DMS at: %s\n", dmsPath)
}
}
fmt.Printf("✓ Found DMS at: %s\n", dmsPath)
if !isGreeterEnabled() {
if nonInteractive {
return fmt.Errorf("greeter is not enabled; run 'dms greeter install' or 'dms greeter enable' first")
}
fmt.Println("\n⚠ DMS greeter is not enabled in greetd config.")
fmt.Print("Would you like to enable it now? (Y/n): ")
@@ -203,9 +286,12 @@ func syncGreeter() error {
}
}
cacheDir := "/var/cache/dms-greeter"
cacheDir := greeter.GreeterCacheDir
if _, err := os.Stat(cacheDir); os.IsNotExist(err) {
return fmt.Errorf("greeter cache directory not found at %s\nPlease install the greeter first", cacheDir)
logFunc("Cache directory not found — attempting to create it...")
if createErr := greeter.EnsureGreeterCacheDir(logFunc, ""); createErr != nil {
return fmt.Errorf("greeter cache directory not found at %s and could not be created: %w\nRun: sudo mkdir -p %s && sudo chown greeter:greeter %s", cacheDir, createErr, cacheDir, cacheDir)
}
}
greeterGroup := greeter.DetectGreeterGroup()
@@ -224,6 +310,9 @@ func syncGreeter() error {
inGreeterGroup := strings.Contains(string(groupsOutput), greeterGroup)
if !inGreeterGroup {
if nonInteractive {
return fmt.Errorf("user must be in the %s group; run 'dms greeter sync' from a terminal to add", greeterGroup)
}
fmt.Printf("\n⚠ Warning: You are not in the %s group.\n", greeterGroup)
fmt.Printf("Would you like to add your user to the %s group? (Y/n): ", greeterGroup)
@@ -255,8 +344,14 @@ func syncGreeter() error {
return fmt.Errorf("no supported compositors found")
case 1:
compositor = compositors[0]
fmt.Printf("✓ Using compositor: %s\n", compositor)
if !nonInteractive {
fmt.Printf("✓ Using compositor: %s\n", compositor)
}
default:
if nonInteractive {
compositor = compositors[0]
break
}
var err error
compositor, err = promptCompositorChoice(compositors)
if err != nil {
@@ -264,27 +359,159 @@ func syncGreeter() error {
}
fmt.Printf("✓ Selected compositor: %s\n", compositor)
}
} else {
} else if !nonInteractive {
fmt.Printf("✓ Detected compositor from config: %s\n", compositor)
}
if local {
localWrapperScript := filepath.Join(dmsPath, "Modules", "Greetd", "assets", "dms-greeter")
restoreWrapperOverride := func() {}
if info, statErr := os.Stat(localWrapperScript); statErr == nil && !info.IsDir() {
previousWrapperOverride, hadWrapperOverride := os.LookupEnv("DMS_GREETER_WRAPPER_CMD")
wrapperCmdOverride := "/usr/bin/bash " + localWrapperScript
_ = os.Setenv("DMS_GREETER_WRAPPER_CMD", wrapperCmdOverride)
restoreWrapperOverride = func() {
if hadWrapperOverride {
_ = os.Setenv("DMS_GREETER_WRAPPER_CMD", previousWrapperOverride)
} else {
_ = os.Unsetenv("DMS_GREETER_WRAPPER_CMD")
}
}
if !nonInteractive {
fmt.Printf("✓ Using local greeter wrapper script: %s\n", localWrapperScript)
}
} else if !nonInteractive {
fmt.Printf(" Local wrapper script not found at %s; using system wrapper.\n", localWrapperScript)
}
fmt.Println("\nUpdating greetd command to use local DMS path...")
err := greeter.ConfigureGreetd(dmsPath, compositor, logFunc, "")
restoreWrapperOverride()
if err != nil {
return fmt.Errorf("failed to apply local greeter path: %w", err)
}
if !nonInteractive {
fmt.Println(" Local mode applies both DMS path override (-p) and local wrapper behavior when available.")
}
} else {
greeterPathForConfig := ""
if !greeter.IsGreeterPackaged() {
greeterPathForConfig = dmsPath
}
fmt.Println("\nUpdating greetd command...")
if err := greeter.ConfigureGreetd(greeterPathForConfig, compositor, logFunc, ""); err != nil {
return fmt.Errorf("failed to update greetd command: %w", err)
}
}
fmt.Println("\nSetting up permissions and ACLs...")
if err := greeter.SetupDMSGroup(logFunc, ""); err != nil {
return err
}
fmt.Println("\nSynchronizing DMS configurations...")
if err := greeter.SyncDMSConfigs(dmsPath, compositor, logFunc, ""); err != nil {
if err := greeter.SyncDMSConfigs(dmsPath, compositor, logFunc, "", forceAuth); err != nil {
return err
}
fmt.Println("\n=== Sync Complete ===")
fmt.Println("\nYour theme, settings, and wallpaper configuration have been synced with the greeter.")
if forceAuth {
fmt.Println("PAM has been configured for fingerprint and U2F (where modules exist).")
}
fmt.Println("The changes will be visible on the next login screen.")
return nil
}
func hasDmsShellQml(dir string) bool {
info, err := os.Stat(filepath.Join(dir, "shell.qml"))
return err == nil && !info.IsDir()
}
func resolveDMSLocalCandidate(path string) (string, bool) {
if path == "" {
return "", false
}
if hasDmsShellQml(path) {
abs, err := filepath.Abs(path)
if err != nil {
return path, true
}
return abs, true
}
quickshellPath := filepath.Join(path, "quickshell")
if hasDmsShellQml(quickshellPath) {
abs, err := filepath.Abs(quickshellPath)
if err != nil {
return quickshellPath, true
}
return abs, true
}
return "", false
}
func resolveLocalDMSPath() (string, error) {
if override := strings.TrimSpace(os.Getenv("DMS_LOCAL_PATH")); override != "" {
if resolved, ok := resolveDMSLocalCandidate(override); ok {
return resolved, nil
}
return "", fmt.Errorf("DMS_LOCAL_PATH is set but does not point to a valid DMS quickshell path: %s", override)
}
wd, err := os.Getwd()
if err != nil {
return "", fmt.Errorf("failed to get current directory: %w", err)
}
dir := wd
for {
if resolved, ok := resolveDMSLocalCandidate(dir); ok {
return resolved, nil
}
parent := filepath.Dir(dir)
if parent == dir {
break
}
dir = parent
}
homeDir, err := os.UserHomeDir()
if err == nil && homeDir != "" {
for _, candidate := range []string{
filepath.Join(homeDir, "dms"),
filepath.Join(homeDir, "DankMaterialShell"),
filepath.Join(homeDir, "dankmaterialshell"),
filepath.Join(homeDir, "projects", "dms"),
filepath.Join(homeDir, "src", "dms"),
} {
if resolved, ok := resolveDMSLocalCandidate(candidate); ok {
return resolved, nil
}
}
if entries, readErr := os.ReadDir(homeDir); readErr == nil {
for _, entry := range entries {
if !entry.IsDir() {
continue
}
name := strings.ToLower(entry.Name())
if !strings.Contains(name, "dms") && !strings.Contains(name, "dank") {
continue
}
if resolved, ok := resolveDMSLocalCandidate(filepath.Join(homeDir, entry.Name())); ok {
return resolved, nil
}
}
}
}
return "", fmt.Errorf("could not locate a local DMS checkout from %s; run from repo root or set DMS_LOCAL_PATH=/absolute/path/to/repo", wd)
}
func disableDisplayManager(dmName string) (bool, error) {
state, err := getSystemdServiceState(dmName)
if err != nil {
@@ -497,12 +724,20 @@ func enableGreeter() error {
configAlreadyCorrect := isGreeterEnabled()
configuredCompositor := detectConfiguredCompositor()
logFunc := func(msg string) {
fmt.Println(msg)
}
if configAlreadyCorrect {
fmt.Println("✓ Greeter is already configured with dms-greeter")
if configuredCompositor != "" {
fmt.Printf("✓ Configured compositor: %s\n", configuredCompositor)
}
if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
fmt.Printf("⚠ Could not create cache directory: %v\n Run: sudo mkdir -p %s && sudo chown greeter:greeter %s\n", err, greeter.GreeterCacheDir, greeter.GreeterCacheDir)
}
if err := ensureGraphicalTarget(); err != nil {
return err
}
@@ -556,13 +791,14 @@ func enableGreeter() error {
}
greeterPathForConfig = dmsPath
}
logFunc := func(msg string) {
fmt.Println(msg)
}
if err := greeter.ConfigureGreetd(greeterPathForConfig, selectedCompositor, logFunc, ""); err != nil {
return fmt.Errorf("failed to configure greetd: %w", err)
}
if err := greeter.EnsureGreeterCacheDir(logFunc, ""); err != nil {
fmt.Printf("⚠ Could not create cache directory: %v\n Run: sudo mkdir -p %s && sudo chown greeter:greeter %s\n", err, greeter.GreeterCacheDir, greeter.GreeterCacheDir)
}
if err := ensureGraphicalTarget(); err != nil {
return err
}
@@ -653,6 +889,95 @@ func readDefaultSessionCommand(configPath string) string {
return ""
}
func extractGreeterCacheDirFromCommand(command string) string {
if command == "" {
return greeter.GreeterCacheDir
}
tokens := strings.Fields(command)
for i := 0; i < len(tokens); i++ {
token := strings.Trim(tokens[i], "\"")
if token == "--cache-dir" && i+1 < len(tokens) {
return strings.Trim(tokens[i+1], "\"")
}
if strings.HasPrefix(token, "--cache-dir=") {
value := strings.TrimPrefix(token, "--cache-dir=")
value = strings.Trim(value, "\"")
if value != "" {
return value
}
}
}
return greeter.GreeterCacheDir
}
func extractGreeterWrapperFromCommand(command string) string {
if command == "" {
return ""
}
tokens := strings.Fields(command)
if len(tokens) == 0 {
return ""
}
return strings.Trim(tokens[0], "\"")
}
func extractGreeterPathOverrideFromCommand(command string) string {
if command == "" {
return ""
}
tokens := strings.Fields(command)
for i := 0; i < len(tokens); i++ {
token := strings.Trim(tokens[i], "\"")
if (token == "-p" || token == "--path") && i+1 < len(tokens) {
return strings.Trim(tokens[i+1], "\"")
}
if strings.HasPrefix(token, "--path=") {
value := strings.TrimPrefix(token, "--path=")
value = strings.Trim(value, "\"")
if value != "" {
return value
}
}
}
return ""
}
func parseManagedGreeterPamAuth(pamText string) (managed bool, fingerprint bool, u2f bool, legacy bool) {
if pamText == "" {
return false, false, false, false
}
lines := strings.Split(pamText, "\n")
inManaged := false
for _, line := range lines {
trimmed := strings.TrimSpace(line)
switch trimmed {
case greeter.GreeterPamManagedBlockStart:
managed = true
inManaged = true
continue
case greeter.GreeterPamManagedBlockEnd:
inManaged = false
continue
}
if strings.HasPrefix(trimmed, "# DMS greeter fingerprint") || strings.HasPrefix(trimmed, "# DMS greeter U2F") {
legacy = true
}
if !inManaged {
continue
}
if strings.Contains(trimmed, "pam_fprintd") {
fingerprint = true
}
if strings.Contains(trimmed, "pam_u2f") {
u2f = true
}
}
return managed, fingerprint, u2f, legacy
}
func packageInstallHint() string {
osInfo, err := distros.GetOSInfo()
if err != nil {
@@ -731,11 +1056,19 @@ func checkGreeterStatus() error {
}
configPath := "/etc/greetd/config.toml"
configuredCommand := ""
allGood := true
fmt.Println("Greeter Configuration:")
if _, err := os.ReadFile(configPath); err == nil {
command := readDefaultSessionCommand(configPath)
if command != "" && strings.Contains(command, "dms-greeter") {
configuredCommand = readDefaultSessionCommand(configPath)
if configuredCommand != "" && strings.Contains(configuredCommand, "dms-greeter") {
fmt.Println(" ✓ Greeter is enabled")
if wrapper := extractGreeterWrapperFromCommand(configuredCommand); wrapper != "" {
fmt.Printf(" Wrapper: %s\n", wrapper)
}
if pathOverride := extractGreeterPathOverrideFromCommand(configuredCommand); pathOverride != "" {
fmt.Printf(" DMS path override: %s\n", pathOverride)
}
compositor := detectConfiguredCompositor()
switch compositor {
@@ -751,10 +1084,12 @@ func checkGreeterStatus() error {
} else {
fmt.Println(" ✗ Greeter is NOT enabled")
fmt.Println(" Run 'dms greeter enable' to enable it")
allGood = false
}
} else {
fmt.Println(" ✗ Greeter config not found")
fmt.Printf(" %s\n", packageInstallHint())
allGood = false
}
fmt.Println("\nGroup Membership:")
@@ -773,8 +1108,12 @@ func checkGreeterStatus() error {
fmt.Println(" Run 'dms greeter sync' to set up group membership and permissions")
}
cacheDir := "/var/cache/dms-greeter"
cacheDir := extractGreeterCacheDirFromCommand(configuredCommand)
fmt.Println("\nGreeter Cache Directory:")
fmt.Printf(" Effective cache dir: %s\n", cacheDir)
if cacheDir != greeter.GreeterCacheDir {
fmt.Printf(" ⚠ Non-default cache dir detected (default: %s)\n", greeter.GreeterCacheDir)
}
if stat, err := os.Stat(cacheDir); err == nil && stat.IsDir() {
fmt.Printf(" ✓ %s exists\n", cacheDir)
} else {
@@ -806,7 +1145,6 @@ func checkGreeterStatus() error {
},
}
allGood := true
for _, link := range symlinks {
targetInfo, err := os.Lstat(link.target)
if err != nil {
@@ -845,11 +1183,80 @@ func checkGreeterStatus() error {
fmt.Printf(" ✓ %s: synced correctly\n", link.desc)
}
fmt.Println("\nGreeter Wallpaper Override:")
overridePath := filepath.Join(cacheDir, "greeter_wallpaper_override.jpg")
if stat, err := os.Stat(overridePath); err == nil && !stat.IsDir() {
fmt.Printf(" ✓ Override file present: %s\n", overridePath)
} else if os.IsNotExist(err) {
fmt.Println(" Override file not present (desktop/session wallpaper fallback in effect)")
} else if err != nil {
fmt.Printf(" ✗ Could not inspect override file: %v\n", err)
allGood = false
} else {
fmt.Printf(" ✗ Override path is not a regular file: %s\n", overridePath)
allGood = false
}
fmt.Println("\nGreeter PAM Authentication (DMS-managed block):")
if greeter.IsNixOS() {
fmt.Println(" NixOS detected: PAM is managed by NixOS modules.")
fmt.Println(" Configure fingerprint/U2F via your greetd NixOS module (security.pam.services.greetd).")
fmt.Println()
if allGood && inGreeterGroup {
fmt.Println("✓ All checks passed! Greeter is properly configured.")
} else if !allGood {
fmt.Println("⚠ Some issues detected. Run 'dms greeter sync' to repair configuration.")
} else if !inGreeterGroup {
fmt.Printf("⚠ User is not in %s group. Run 'dms greeter sync' after adding group membership.\n", greeterGroup)
}
return nil
}
greetdPamPath := "/etc/pam.d/greetd"
pamData, err := os.ReadFile(greetdPamPath)
if err != nil {
fmt.Printf(" ✗ Failed to read %s: %v\n", greetdPamPath, err)
allGood = false
} else {
managed, managedFprint, managedU2f, legacyManaged := parseManagedGreeterPamAuth(string(pamData))
if managed {
fmt.Println(" ✓ Managed auth block present")
if managedFprint {
fmt.Println(" - fingerprint: enabled")
} else {
fmt.Println(" - fingerprint: disabled")
}
if managedU2f {
fmt.Println(" - security key (U2F): enabled")
} else {
fmt.Println(" - security key (U2F): disabled")
}
} else {
fmt.Println(" No managed auth block present (fingerprint/U2F disabled for greeter)")
}
if legacyManaged {
fmt.Println(" ⚠ Legacy unmanaged DMS PAM lines detected. Run 'dms greeter sync' to normalize.")
allGood = false
}
includedFprintFile := greeter.DetectIncludedPamModule(string(pamData), "pam_fprintd.so")
if managedFprint {
if includedFprintFile != "" {
fmt.Printf(" ⚠ pam_fprintd found in both DMS managed block and %s.\n", includedFprintFile)
fmt.Println(" Double fingerprint auth detected — run 'dms greeter sync' to resolve.")
allGood = false
}
} else if includedFprintFile != "" {
fmt.Printf(" Fingerprint auth is enabled via included %s.\n", includedFprintFile)
fmt.Println(" The DMS toggle only controls the managed block; disable fingerprint in authselect/pam-auth-update for password-only greeter login.")
}
}
fmt.Println()
if allGood && inGreeterGroup {
fmt.Println("✓ All checks passed! Greeter is properly configured.")
} else if !allGood {
fmt.Println("⚠ Some issues detected. Run 'dms greeter sync' to fix symlinks.")
fmt.Println("⚠ Some issues detected. Run 'dms greeter sync' to repair configuration.")
} else if !inGreeterGroup {
fmt.Printf("⚠ User is not in %s group. Run 'dms greeter sync' after adding group membership.\n", greeterGroup)
}
return nil