From 20ef5e2c1840268d4a176954e518fccad8f820bd Mon Sep 17 00:00:00 2001 From: purian23 Date: Sun, 22 Feb 2026 22:40:51 -0500 Subject: [PATCH] dms-greeter: Enhance DMS Greeter dankinstall & packaging across distros - Added support for Debian, Ubuntu, Fedora, Arch, and OpenSUSE on dankinstall / dms greeter install --- core/cmd/dms/commands_greeter.go | 122 +++++++++++++++--- core/internal/distros/arch.go | 6 + core/internal/distros/debian.go | 6 + core/internal/distros/fedora.go | 6 + core/internal/distros/opensuse.go | 6 + core/internal/distros/ubuntu.go | 6 + core/internal/greeter/installer.go | 191 ++++++++++++++++++++++++++++- 7 files changed, 319 insertions(+), 24 deletions(-) diff --git a/core/cmd/dms/commands_greeter.go b/core/cmd/dms/commands_greeter.go index e9de1c02..5db3acef 100644 --- a/core/cmd/dms/commands_greeter.go +++ b/core/cmd/dms/commands_greeter.go @@ -8,6 +8,7 @@ import ( "path/filepath" "strings" + "github.com/AvengeMedia/DankMaterialShell/core/internal/distros" "github.com/AvengeMedia/DankMaterialShell/core/internal/greeter" "github.com/AvengeMedia/DankMaterialShell/core/internal/log" "github.com/AvengeMedia/DankMaterialShell/core/internal/utils" @@ -77,6 +78,28 @@ func installGreeter() error { return err } + // Debian/openSUSE + greeter.TryInstallGreeterPackage(logFunc, "") + if isPackageOnlyGreeterDistro() && !greeter.IsGreeterPackaged() { + return fmt.Errorf("dms-greeter must be installed from distro packages on this distribution. %s", packageInstallHint()) + } + 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") + } + + // If already fully configured, prompt the user + if isGreeterEnabled() { + fmt.Print("\nGreeter is already installed and configured. Re-run to re-sync settings and permissions? [Y/n]: ") + var response string + fmt.Scanln(&response) + response = strings.TrimSpace(strings.ToLower(response)) + if response == "n" || response == "no" { + fmt.Println("Run 'dms greeter sync' to re-sync theme and settings at any time.") + return nil + } + fmt.Println() + } + fmt.Println("\nDetecting DMS installation...") dmsPath, err := greeter.DetectDMSPath() if err != nil { @@ -114,7 +137,12 @@ func installGreeter() error { } fmt.Println("\nConfiguring greetd...") - if err := greeter.ConfigureGreetd(dmsPath, selectedCompositor, logFunc, ""); err != nil { + // Use empty path when packaged (greeter finds /usr/share/quickshell/dms-greeter); else use user's DMS path + greeterPathForConfig := "" + if !greeter.IsGreeterPackaged() { + greeterPathForConfig = dmsPath + } + if err := greeter.ConfigureGreetd(greeterPathForConfig, selectedCompositor, logFunc, ""); err != nil { return err } @@ -327,20 +355,29 @@ func ensureGreetdEnabled() error { fmt.Println(" ✓ Unmasked greetd") } - switch state.EnabledState { - case "disabled", "masked", "masked-runtime": + if state.EnabledState == "enabled" || state.EnabledState == "enabled-runtime" { + fmt.Println(" Reasserting greetd as active display manager...") + } else { fmt.Println(" Enabling greetd service...") - enableCmd := exec.Command("sudo", "systemctl", "enable", "greetd") - enableCmd.Stdout = os.Stdout - enableCmd.Stderr = os.Stderr - if err := enableCmd.Run(); err != nil { - return fmt.Errorf("failed to enable greetd: %w", err) + } + + enableCmd := exec.Command("sudo", "systemctl", "enable", "--force", "greetd") + enableCmd.Stdout = os.Stdout + enableCmd.Stderr = os.Stderr + if err := enableCmd.Run(); err != nil { + return fmt.Errorf("failed to enable greetd: %w", err) + } + + enabledState, _, verifyErr := checkSystemdServiceEnabled("greetd") + if verifyErr != nil { + fmt.Printf(" ⚠ Warning: Could not verify greetd enabled state: %v\n", verifyErr) + } else { + switch enabledState { + case "enabled", "enabled-runtime", "static", "indirect", "alias": + fmt.Printf(" ✓ greetd enabled (state: %s)\n", enabledState) + default: + return fmt.Errorf("greetd is still in state '%s' after enable operation", enabledState) } - fmt.Println(" ✓ Enabled greetd service") - case "enabled", "enabled-runtime": - fmt.Println(" ✓ greetd is already enabled") - default: - fmt.Printf(" ℹ greetd is in state '%s' (should work, no action needed)\n", state.EnabledState) } return nil @@ -446,6 +483,10 @@ func enableGreeter() error { } 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") if configAlreadyCorrect { @@ -611,6 +652,48 @@ func detectConfiguredCompositor() string { return "" } +func packageInstallHint() string { + osInfo, err := distros.GetOSInfo() + if err != nil { + return "Install package: dms-greeter" + } + config, exists := distros.Registry[osInfo.Distribution.ID] + if !exists { + return "Install package: dms-greeter" + } + + switch config.Family { + case distros.FamilyDebian: + return "Install with 'sudo apt install dms-greeter' (requires DankLinux OBS repo — see https://danklinux.com/docs/dankgreeter/installation#debian)" + case distros.FamilySUSE: + return "Install with 'sudo zypper install dms-greeter' (requires DankLinux OBS repo — see https://danklinux.com/docs/dankgreeter/installation#opensuse)" + case distros.FamilyUbuntu: + return "Install with 'sudo apt install dms-greeter' (requires ppa:avengemedia/danklinux: sudo add-apt-repository ppa:avengemedia/danklinux)" + case distros.FamilyFedora: + return "Install with 'sudo dnf install dms-greeter' (requires COPR: sudo dnf copr enable avengemedia/danklinux)" + case distros.FamilyArch: + return "Install from AUR with 'paru -S greetd-dms-greeter-git' or 'yay -S greetd-dms-greeter-git'" + default: + return "Run 'dms greeter install' to install greeter" + } +} + +func isPackageOnlyGreeterDistro() bool { + osInfo, err := distros.GetOSInfo() + if err != nil { + return false + } + config, exists := distros.Registry[osInfo.Distribution.ID] + if !exists { + return false + } + return config.Family == distros.FamilyDebian || + config.Family == distros.FamilySUSE || + config.Family == distros.FamilyUbuntu || + config.Family == distros.FamilyFedora || + config.Family == distros.FamilyArch +} + func promptCompositorChoice(compositors []string) (string, error) { fmt.Println("\nMultiple compositors detected:") for i, comp := range compositors { @@ -679,7 +762,7 @@ func checkGreeterStatus() error { } } else { fmt.Println(" ✗ Greeter config not found") - fmt.Println(" Run 'dms greeter install' to install greeter") + fmt.Printf(" %s\n", packageInstallHint()) } fmt.Println("\nGroup Membership:") @@ -689,12 +772,13 @@ func checkGreeterStatus() error { return fmt.Errorf("failed to check groups: %w", err) } - inGreeterGroup := strings.Contains(string(groupsOutput), "greeter") + greeterGroup := greeter.DetectGreeterGroup() + inGreeterGroup := strings.Contains(string(groupsOutput), greeterGroup) if inGreeterGroup { - fmt.Println(" ✓ User is in greeter group") + fmt.Printf(" ✓ User is in %s group\n", greeterGroup) } else { - fmt.Println(" ✗ User is NOT in greeter group") - fmt.Println(" Run 'dms greeter install' to add user to greeter group") + fmt.Printf(" ✗ User is NOT in %s group\n", greeterGroup) + fmt.Println(" Run 'dms greeter sync' to set up group membership and permissions") } cacheDir := "/var/cache/dms-greeter" @@ -703,7 +787,7 @@ func checkGreeterStatus() error { fmt.Printf(" ✓ %s exists\n", cacheDir) } else { fmt.Printf(" ✗ %s not found\n", cacheDir) - fmt.Println(" Run 'dms greeter install' to create cache directory") + fmt.Printf(" %s\n", packageInstallHint()) return nil } diff --git a/core/internal/distros/arch.go b/core/internal/distros/arch.go index 82371b0c..9b2c1087 100644 --- a/core/internal/distros/arch.go +++ b/core/internal/distros/arch.go @@ -97,6 +97,7 @@ func (a *ArchDistribution) DetectDependenciesWithTerminal(ctx context.Context, w dependencies = append(dependencies, a.detectGit()) dependencies = append(dependencies, a.detectWindowManager(wm)) dependencies = append(dependencies, a.detectQuickshell()) + dependencies = append(dependencies, a.detectDMSGreeter()) dependencies = append(dependencies, a.detectXDGPortal()) dependencies = append(dependencies, a.detectAccountsService()) @@ -124,6 +125,10 @@ func (a *ArchDistribution) detectAccountsService() deps.Dependency { return a.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", a.packageInstalled("accountsservice")) } +func (a *ArchDistribution) detectDMSGreeter() deps.Dependency { + return a.detectPackage("dms-greeter", "DankMaterialShell greetd greeter", a.packageInstalled("greetd-dms-greeter-git")) +} + func (a *ArchDistribution) packageInstalled(pkg string) bool { cmd := exec.Command("pacman", "-Q", pkg) err := cmd.Run() @@ -139,6 +144,7 @@ func (a *ArchDistribution) GetPackageMappingWithVariants(wm deps.WindowManager, "dms (DankMaterialShell)": a.getDMSMapping(variants["dms (DankMaterialShell)"]), "git": {Name: "git", Repository: RepoTypeSystem}, "quickshell": a.getQuickshellMapping(variants["quickshell"]), + "dms-greeter": {Name: "greetd-dms-greeter-git", Repository: RepoTypeAUR}, "matugen": a.getMatugenMapping(variants["matugen"]), "dgop": {Name: "dgop", Repository: RepoTypeSystem}, "ghostty": {Name: "ghostty", Repository: RepoTypeSystem}, diff --git a/core/internal/distros/debian.go b/core/internal/distros/debian.go index 0f1721b7..5748bc7e 100644 --- a/core/internal/distros/debian.go +++ b/core/internal/distros/debian.go @@ -61,6 +61,7 @@ func (d *DebianDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, d.detectGit()) dependencies = append(dependencies, d.detectWindowManager(wm)) dependencies = append(dependencies, d.detectQuickshell()) + dependencies = append(dependencies, d.detectDMSGreeter()) dependencies = append(dependencies, d.detectXDGPortal()) dependencies = append(dependencies, d.detectAccountsService()) @@ -86,6 +87,10 @@ func (d *DebianDistribution) detectAccountsService() deps.Dependency { return d.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", d.packageInstalled("accountsservice")) } +func (d *DebianDistribution) detectDMSGreeter() deps.Dependency { + return d.detectPackage("dms-greeter", "DankMaterialShell greetd greeter", d.packageInstalled("dms-greeter")) +} + func (d *DebianDistribution) packageInstalled(pkg string) bool { cmd := exec.Command("dpkg", "-l", pkg) err := cmd.Run() @@ -108,6 +113,7 @@ func (d *DebianDistribution) GetPackageMappingWithVariants(wm deps.WindowManager // DMS packages from OBS with variant support "dms (DankMaterialShell)": d.getDmsMapping(variants["dms (DankMaterialShell)"]), "quickshell": d.getQuickshellMapping(variants["quickshell"]), + "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, diff --git a/core/internal/distros/fedora.go b/core/internal/distros/fedora.go index 72020967..d2574290 100644 --- a/core/internal/distros/fedora.go +++ b/core/internal/distros/fedora.go @@ -78,6 +78,7 @@ func (f *FedoraDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, f.detectGit()) dependencies = append(dependencies, f.detectWindowManager(wm)) dependencies = append(dependencies, f.detectQuickshell()) + dependencies = append(dependencies, f.detectDMSGreeter()) dependencies = append(dependencies, f.detectXDGPortal()) dependencies = append(dependencies, f.detectAccountsService()) @@ -123,6 +124,7 @@ func (f *FedoraDistribution) GetPackageMappingWithVariants(wm deps.WindowManager // COPR packages "quickshell": f.getQuickshellMapping(variants["quickshell"]), + "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, "dms (DankMaterialShell)": f.getDmsMapping(variants["dms (DankMaterialShell)"]), "dgop": {Name: "dgop", Repository: RepoTypeCOPR, RepoURL: "avengemedia/danklinux"}, @@ -194,6 +196,10 @@ func (f *FedoraDistribution) detectAccountsService() deps.Dependency { } } +func (f *FedoraDistribution) detectDMSGreeter() deps.Dependency { + return f.detectPackage("dms-greeter", "DankMaterialShell greetd greeter", f.packageInstalled("dms-greeter")) +} + func (f *FedoraDistribution) getPrerequisites() []string { return []string{ "dnf-plugins-core", diff --git a/core/internal/distros/opensuse.go b/core/internal/distros/opensuse.go index dfee885d..5b7927f5 100644 --- a/core/internal/distros/opensuse.go +++ b/core/internal/distros/opensuse.go @@ -71,6 +71,7 @@ func (o *OpenSUSEDistribution) DetectDependenciesWithTerminal(ctx context.Contex dependencies = append(dependencies, o.detectGit()) dependencies = append(dependencies, o.detectWindowManager(wm)) dependencies = append(dependencies, o.detectQuickshell()) + dependencies = append(dependencies, o.detectDMSGreeter()) dependencies = append(dependencies, o.detectXDGPortal()) dependencies = append(dependencies, o.detectAccountsService()) @@ -100,6 +101,10 @@ func (o *OpenSUSEDistribution) packageInstalled(pkg string) bool { return err == nil } +func (o *OpenSUSEDistribution) detectDMSGreeter() deps.Dependency { + return o.detectPackage("dms-greeter", "DankMaterialShell greetd greeter", o.packageInstalled("dms-greeter")) +} + func (o *OpenSUSEDistribution) GetPackageMapping(wm deps.WindowManager) map[string]PackageMapping { return o.GetPackageMappingWithVariants(wm, make(map[string]deps.PackageVariant)) } @@ -116,6 +121,7 @@ func (o *OpenSUSEDistribution) GetPackageMappingWithVariants(wm deps.WindowManag // DMS packages from OBS "dms (DankMaterialShell)": o.getDmsMapping(variants["dms (DankMaterialShell)"]), "quickshell": o.getQuickshellMapping(variants["quickshell"]), + "dms-greeter": {Name: "dms-greeter", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "ghostty": {Name: "ghostty", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypeOBS, RepoURL: "home:AvengeMedia:danklinux"}, diff --git a/core/internal/distros/ubuntu.go b/core/internal/distros/ubuntu.go index fbb05fb7..6bd9f40f 100644 --- a/core/internal/distros/ubuntu.go +++ b/core/internal/distros/ubuntu.go @@ -63,6 +63,7 @@ func (u *UbuntuDistribution) DetectDependenciesWithTerminal(ctx context.Context, dependencies = append(dependencies, u.detectGit()) dependencies = append(dependencies, u.detectWindowManager(wm)) dependencies = append(dependencies, u.detectQuickshell()) + dependencies = append(dependencies, u.detectDMSGreeter()) dependencies = append(dependencies, u.detectXDGPortal()) dependencies = append(dependencies, u.detectAccountsService()) @@ -94,6 +95,10 @@ func (u *UbuntuDistribution) detectAccountsService() deps.Dependency { return u.detectPackage("accountsservice", "D-Bus interface for user account query and manipulation", u.packageInstalled("accountsservice")) } +func (u *UbuntuDistribution) detectDMSGreeter() deps.Dependency { + return u.detectPackage("dms-greeter", "DankMaterialShell greetd greeter", u.packageInstalled("dms-greeter")) +} + func (u *UbuntuDistribution) packageInstalled(pkg string) bool { cmd := exec.Command("dpkg", "-l", pkg) err := cmd.Run() @@ -116,6 +121,7 @@ func (u *UbuntuDistribution) GetPackageMappingWithVariants(wm deps.WindowManager // DMS packages from PPAs "dms (DankMaterialShell)": u.getDmsMapping(variants["dms (DankMaterialShell)"]), "quickshell": u.getQuickshellMapping(variants["quickshell"]), + "dms-greeter": {Name: "dms-greeter", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, "matugen": {Name: "matugen", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, "dgop": {Name: "dgop", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, "ghostty": {Name: "ghostty", Repository: RepoTypePPA, RepoURL: "ppa:avengemedia/danklinux"}, diff --git a/core/internal/greeter/installer.go b/core/internal/greeter/installer.go index ecef0345..e2b8e2b4 100644 --- a/core/internal/greeter/installer.go +++ b/core/internal/greeter/installer.go @@ -79,9 +79,18 @@ func PromptCompositorChoice(compositors []string) (string, error) { } } -// EnsureGreetdInstalled checks if greetd is installed and installs it if not +// EnsureGreetdInstalled checks if greetd is installed - greetd is a daemon in /usr/sbin on Debian/Ubuntu func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error { - if utils.CommandExists("greetd") { + greetdFound := utils.CommandExists("greetd") + if !greetdFound { + for _, p := range []string{"/usr/sbin/greetd", "/sbin/greetd"} { + if _, err := os.Stat(p); err == nil { + greetdFound = true + break + } + } + } + if greetdFound { logFunc("✓ greetd is already installed") return nil } @@ -142,6 +151,14 @@ func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error { installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "greetd") } + case distros.FamilyGentoo: + if sudoPassword != "" { + installCmd = distros.ExecSudoCommand(ctx, sudoPassword, + "emerge --ask n sys-apps/greetd") + } else { + installCmd = exec.CommandContext(ctx, "sudo", "emerge", "--ask", "n", "sys-apps/greetd") + } + case distros.FamilyNix: return fmt.Errorf("on NixOS, please add greetd to your configuration.nix") @@ -160,6 +177,119 @@ func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error { return nil } +// IsGreeterPackaged returns true if dms-greeter was installed from a system package. +func IsGreeterPackaged() bool { + if !utils.CommandExists("dms-greeter") { + return false + } + packagedPath := "/usr/share/quickshell/dms-greeter" + info, err := os.Stat(packagedPath) + return err == nil && info.IsDir() +} + +// HasLegacyLocalGreeterWrapper returns true when a manually installed wrapper exists. +func HasLegacyLocalGreeterWrapper() bool { + info, err := os.Stat("/usr/local/bin/dms-greeter") + return err == nil && !info.IsDir() +} + +// TryInstallGreeterPackage attempts to install dms-greeter from the distro's official repo. +func TryInstallGreeterPackage(logFunc func(string), sudoPassword string) bool { + osInfo, err := distros.GetOSInfo() + if err != nil { + return false + } + config, exists := distros.Registry[osInfo.Distribution.ID] + if !exists { + return false + } + + if IsGreeterPackaged() { + logFunc("✓ dms-greeter package already installed") + return true + } + + ctx := context.Background() + var installCmd *exec.Cmd + var failHint string + + switch config.Family { + case distros.FamilyDebian: + obsSlug := getDebianOBSSlug(osInfo) + keyURL := fmt.Sprintf("https://download.opensuse.org/repositories/home:AvengeMedia:danklinux/%s/Release.key", obsSlug) + repoLine := fmt.Sprintf("deb [signed-by=/etc/apt/keyrings/danklinux.gpg] https://download.opensuse.org/repositories/home:/AvengeMedia:/danklinux/%s/ /", obsSlug) + failHint = fmt.Sprintf("⚠ dms-greeter install failed. Add OBS repo manually:\ncurl -fsSL %s | sudo gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg\necho '%s' | sudo tee /etc/apt/sources.list.d/danklinux.list\nsudo apt update && sudo apt install dms-greeter", keyURL, repoLine) + logFunc(fmt.Sprintf("Adding DankLinux OBS repository (%s)...", obsSlug)) + addKeyCmd := exec.CommandContext(ctx, "bash", "-c", + fmt.Sprintf(`curl -fsSL %s | sudo gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg`, keyURL)) + addKeyCmd.Stdout = os.Stdout + addKeyCmd.Stderr = os.Stderr + addKeyCmd.Run() + addRepoCmd := exec.CommandContext(ctx, "bash", "-c", + fmt.Sprintf(`echo '%s' | sudo tee /etc/apt/sources.list.d/danklinux.list`, repoLine)) + addRepoCmd.Stdout = os.Stdout + addRepoCmd.Stderr = os.Stderr + addRepoCmd.Run() + exec.CommandContext(ctx, "sudo", "apt-get", "update").Run() + installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "dms-greeter") + case distros.FamilySUSE: + repoURL := getOpenSUSEOBSRepoURL(osInfo) + failHint = fmt.Sprintf("⚠ dms-greeter install failed. Add OBS repo manually:\nsudo zypper addrepo %s\nsudo zypper refresh && sudo zypper install dms-greeter", repoURL) + logFunc("Adding DankLinux OBS repository...") + addRepoCmd := exec.CommandContext(ctx, "sudo", "zypper", "addrepo", repoURL) + addRepoCmd.Stdout = os.Stdout + addRepoCmd.Stderr = os.Stderr + addRepoCmd.Run() + exec.CommandContext(ctx, "sudo", "zypper", "refresh").Run() + installCmd = exec.CommandContext(ctx, "sudo", "zypper", "install", "-y", "dms-greeter") + case distros.FamilyUbuntu: + failHint = "⚠ dms-greeter install failed. Add PPA manually: sudo add-apt-repository ppa:avengemedia/danklinux && sudo apt-get update && sudo apt-get install dms-greeter" + logFunc("Enabling PPA ppa:avengemedia/danklinux...") + ppacmd := exec.CommandContext(ctx, "sudo", "add-apt-repository", "-y", "ppa:avengemedia/danklinux") + ppacmd.Stdout = os.Stdout + ppacmd.Stderr = os.Stderr + ppacmd.Run() + exec.CommandContext(ctx, "sudo", "apt-get", "update").Run() + installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "dms-greeter") + case distros.FamilyFedora: + failHint = "⚠ dms-greeter install failed. Enable COPR manually: sudo dnf copr enable avengemedia/danklinux && sudo dnf install dms-greeter" + logFunc("Enabling COPR avengemedia/danklinux...") + coprcmd := exec.CommandContext(ctx, "sudo", "dnf", "copr", "enable", "-y", "avengemedia/danklinux") + coprcmd.Stdout = os.Stdout + coprcmd.Stderr = os.Stderr + coprcmd.Run() + installCmd = exec.CommandContext(ctx, "sudo", "dnf", "install", "-y", "dms-greeter") + case distros.FamilyArch: + aurHelper := "" + for _, helper := range []string{"paru", "yay"} { + if _, err := exec.LookPath(helper); err == nil { + aurHelper = helper + break + } + } + if aurHelper == "" { + logFunc("⚠ No AUR helper found (paru/yay). Install greetd-dms-greeter-git from AUR: https://aur.archlinux.org/packages/greetd-dms-greeter-git") + return false + } + failHint = fmt.Sprintf("⚠ dms-greeter install failed. Install from AUR: %s -S greetd-dms-greeter-git", aurHelper) + installCmd = exec.CommandContext(ctx, aurHelper, "-S", "--noconfirm", "greetd-dms-greeter-git") + default: + return false + } + + logFunc("Installing dms-greeter from official repository...") + installCmd.Stdout = os.Stdout + installCmd.Stderr = os.Stderr + + if err := installCmd.Run(); err != nil { + logFunc(failHint) + return false + } + + logFunc("✓ dms-greeter package installed") + return true +} + // CopyGreeterFiles installs the dms-greeter wrapper and sets up cache directory func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPassword string) error { // Check if dms-greeter is already in PATH @@ -393,7 +523,7 @@ func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPasswo } } - runSudoCmd(sudoPassword, "rm", "-f", link.target) //nolint:errcheck + _ = runSudoCmd(sudoPassword, "rm", "-f", link.target) if err := runSudoCmd(sudoPassword, "ln", "-sf", link.source, link.target); err != nil { logFunc(fmt.Sprintf("⚠ Warning: Failed to create symlink for %s: %v", link.desc, err)) @@ -794,8 +924,14 @@ user = "%s" } // 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) - command := fmt.Sprintf(`command = "%s --command %s -p %s"`, wrapperCmd, compositorLower, dmsPath) + 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 @@ -832,7 +968,11 @@ user = "%s" return fmt.Errorf("failed to move config to /etc/greetd: %w", err) } - logFunc(fmt.Sprintf("✓ Updated greetd configuration (user: %s, command: %s --command %s -p %s)", greeterUser, wrapperCmd, compositorLower, dmsPath)) + cmdDesc := fmt.Sprintf("%s --command %s", wrapperCmd, compositorLower) + if dmsPath != "" { + cmdDesc = fmt.Sprintf("%s -p %s", cmdDesc, dmsPath) + } + logFunc(fmt.Sprintf("✓ Updated greetd configuration (user: %s, command: %s)", greeterUser, cmdDesc)) return nil } @@ -867,6 +1007,47 @@ func stripConfigFlag(command string) string { return command } +// getDebianOBSSlug returns the OBS repository slug for the running Debian version. +func getDebianOBSSlug(osInfo *distros.OSInfo) string { + versionID := strings.ToLower(osInfo.VersionID) + codename := strings.ToLower(osInfo.VersionCodename) + prettyName := strings.ToLower(osInfo.PrettyName) + + if strings.Contains(prettyName, "sid") || strings.Contains(prettyName, "unstable") || + codename == "sid" || versionID == "sid" { + return "Debian_Unstable" + } + if versionID == "testing" || codename == "testing" { + return "Debian_Testing" + } + if versionID != "" { + return "Debian_" + versionID // "Debian_13" + } + return "Debian_Unstable" +} + +// getOpenSUSEOBSRepoURL returns the OBS .repo file URL for the running openSUSE variant. +func getOpenSUSEOBSRepoURL(osInfo *distros.OSInfo) string { + const base = "https://download.opensuse.org/repositories/home:AvengeMedia:danklinux" + var slug string + switch osInfo.Distribution.ID { + case "opensuse-leap": + v := osInfo.VersionID + if v != "" && !strings.Contains(v, ".") { + v += ".0" // "16" → "16.0" + } + if v == "" { + v = "16.0" + } + slug = v + case "opensuse-slowroll": + slug = "openSUSE_Slowroll" + default: // opensuse-tumbleweed || unknown version + slug = "openSUSE_Tumbleweed" + } + return fmt.Sprintf("%s/%s/home:AvengeMedia:danklinux.repo", base, slug) +} + func runSudoCmd(sudoPassword string, command string, args ...string) error { var cmd *exec.Cmd