mirror of
https://github.com/AvengeMedia/DankMaterialShell.git
synced 2026-04-17 11:12:06 -04:00
core: add privesc package for privilege escalation
- Adds support for run0 and doas fixes #998
This commit is contained in:
@@ -16,9 +16,10 @@ var authCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var authSyncCmd = &cobra.Command{
|
var authSyncCmd = &cobra.Command{
|
||||||
Use: "sync",
|
Use: "sync",
|
||||||
Short: "Sync DMS authentication configuration",
|
Short: "Sync DMS authentication configuration",
|
||||||
Long: "Apply shared PAM/authentication changes for the lock screen and greeter based on current DMS settings",
|
Long: "Apply shared PAM/authentication changes for the lock screen and greeter based on current DMS settings",
|
||||||
|
PreRunE: preRunPrivileged,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
yes, _ := cmd.Flags().GetBool("yes")
|
yes, _ := cmd.Flags().GetBool("yes")
|
||||||
term, _ := cmd.Flags().GetBool("terminal")
|
term, _ := cmd.Flags().GetBool("terminal")
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -15,6 +16,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/errdefs"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@@ -130,12 +132,8 @@ func updateArchLinux() error {
|
|||||||
return errdefs.ErrUpdateCancelled
|
return errdefs.ErrUpdateCancelled
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("\nRunning: sudo pacman -S %s\n", packageName)
|
fmt.Printf("\nRunning: pacman -S %s\n", packageName)
|
||||||
cmd := exec.Command("sudo", "pacman", "-S", "--noconfirm", packageName)
|
if err := privesc.Run(context.Background(), "", "pacman", "-S", "--noconfirm", packageName); err != nil {
|
||||||
cmd.Stdin = os.Stdin
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
fmt.Printf("Error: Failed to update using pacman: %v\n", err)
|
fmt.Printf("Error: Failed to update using pacman: %v\n", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -479,11 +477,7 @@ func updateDMSBinary() error {
|
|||||||
|
|
||||||
fmt.Printf("Installing to %s...\n", currentPath)
|
fmt.Printf("Installing to %s...\n", currentPath)
|
||||||
|
|
||||||
replaceCmd := exec.Command("sudo", "install", "-m", "0755", decompressedPath, currentPath)
|
if err := privesc.Run(context.Background(), "", "install", "-m", "0755", decompressedPath, currentPath); err != nil {
|
||||||
replaceCmd.Stdin = os.Stdin
|
|
||||||
replaceCmd.Stdout = os.Stdout
|
|
||||||
replaceCmd.Stderr = os.Stderr
|
|
||||||
if err := replaceCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to replace binary: %w", err)
|
return fmt.Errorf("failed to replace binary: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@@ -14,6 +15,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/log"
|
||||||
sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
|
sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/text/cases"
|
"golang.org/x/text/cases"
|
||||||
@@ -35,7 +37,7 @@ var greeterInstallCmd = &cobra.Command{
|
|||||||
Use: "install",
|
Use: "install",
|
||||||
Short: "Install and configure DMS greeter",
|
Short: "Install and configure DMS greeter",
|
||||||
Long: "Install greetd and configure it to use DMS as the greeter interface",
|
Long: "Install greetd and configure it to use DMS as the greeter interface",
|
||||||
PreRunE: requireMutableSystemCommand,
|
PreRunE: preRunPrivileged,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
yes, _ := cmd.Flags().GetBool("yes")
|
yes, _ := cmd.Flags().GetBool("yes")
|
||||||
term, _ := cmd.Flags().GetBool("terminal")
|
term, _ := cmd.Flags().GetBool("terminal")
|
||||||
@@ -57,9 +59,10 @@ var greeterInstallCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var greeterSyncCmd = &cobra.Command{
|
var greeterSyncCmd = &cobra.Command{
|
||||||
Use: "sync",
|
Use: "sync",
|
||||||
Short: "Sync DMS theme and settings with greeter",
|
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",
|
Long: "Synchronize your current user's DMS theme, settings, and wallpaper configuration with the login greeter screen",
|
||||||
|
PreRunE: preRunPrivileged,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
yes, _ := cmd.Flags().GetBool("yes")
|
yes, _ := cmd.Flags().GetBool("yes")
|
||||||
auth, _ := cmd.Flags().GetBool("auth")
|
auth, _ := cmd.Flags().GetBool("auth")
|
||||||
@@ -88,7 +91,7 @@ var greeterEnableCmd = &cobra.Command{
|
|||||||
Use: "enable",
|
Use: "enable",
|
||||||
Short: "Enable DMS greeter in greetd config",
|
Short: "Enable DMS greeter in greetd config",
|
||||||
Long: "Configure greetd to use DMS as the greeter",
|
Long: "Configure greetd to use DMS as the greeter",
|
||||||
PreRunE: requireMutableSystemCommand,
|
PreRunE: preRunPrivileged,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
yes, _ := cmd.Flags().GetBool("yes")
|
yes, _ := cmd.Flags().GetBool("yes")
|
||||||
term, _ := cmd.Flags().GetBool("terminal")
|
term, _ := cmd.Flags().GetBool("terminal")
|
||||||
@@ -124,7 +127,7 @@ var greeterUninstallCmd = &cobra.Command{
|
|||||||
Use: "uninstall",
|
Use: "uninstall",
|
||||||
Short: "Remove DMS greeter configuration and restore previous display manager",
|
Short: "Remove DMS greeter configuration and restore previous display manager",
|
||||||
Long: "Disable greetd, remove DMS managed configs, and restore the system to its pre-DMS-greeter state",
|
Long: "Disable greetd, remove DMS managed configs, and restore the system to its pre-DMS-greeter state",
|
||||||
PreRunE: requireMutableSystemCommand,
|
PreRunE: preRunPrivileged,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
yes, _ := cmd.Flags().GetBool("yes")
|
yes, _ := cmd.Flags().GetBool("yes")
|
||||||
term, _ := cmd.Flags().GetBool("terminal")
|
term, _ := cmd.Flags().GetBool("terminal")
|
||||||
@@ -306,10 +309,7 @@ func uninstallGreeter(nonInteractive bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("\nDisabling greetd...")
|
fmt.Println("\nDisabling greetd...")
|
||||||
disableCmd := exec.Command("sudo", "systemctl", "disable", "greetd")
|
if err := privesc.Run(context.Background(), "", "systemctl", "disable", "greetd"); err != nil {
|
||||||
disableCmd.Stdout = os.Stdout
|
|
||||||
disableCmd.Stderr = os.Stderr
|
|
||||||
if err := disableCmd.Run(); err != nil {
|
|
||||||
fmt.Printf(" ⚠ Could not disable greetd: %v\n", err)
|
fmt.Printf(" ⚠ Could not disable greetd: %v\n", err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Println(" ✓ greetd disabled")
|
fmt.Println(" ✓ greetd disabled")
|
||||||
@@ -375,10 +375,10 @@ func restorePreDMSGreetdConfig(sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
tmp.Close()
|
tmp.Close()
|
||||||
|
|
||||||
if err := runSudoCommand(sudoPassword, "cp", tmpPath, configPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "cp", tmpPath, configPath); err != nil {
|
||||||
return fmt.Errorf("failed to restore %s: %w", candidate, err)
|
return fmt.Errorf("failed to restore %s: %w", candidate, err)
|
||||||
}
|
}
|
||||||
if err := runSudoCommand(sudoPassword, "chmod", "644", configPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "644", configPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
fmt.Printf(" ✓ Restored greetd config from %s\n", candidate)
|
fmt.Printf(" ✓ Restored greetd config from %s\n", candidate)
|
||||||
@@ -406,21 +406,14 @@ command = "agreety --cmd /bin/bash"
|
|||||||
}
|
}
|
||||||
tmp.Close()
|
tmp.Close()
|
||||||
|
|
||||||
if err := runSudoCommand(sudoPassword, "cp", tmpPath, configPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "cp", tmpPath, configPath); err != nil {
|
||||||
return fmt.Errorf("failed to write fallback greetd config: %w", err)
|
return fmt.Errorf("failed to write fallback greetd config: %w", err)
|
||||||
}
|
}
|
||||||
_ = runSudoCommand(sudoPassword, "chmod", "644", configPath)
|
_ = privesc.Run(context.Background(), sudoPassword, "chmod", "644", configPath)
|
||||||
fmt.Println(" ✓ Wrote minimal fallback greetd config (configure a greeter command manually if needed)")
|
fmt.Println(" ✓ Wrote minimal fallback greetd config (configure a greeter command manually if needed)")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSudoCommand(_ string, command string, args ...string) error {
|
|
||||||
cmd := exec.Command("sudo", append([]string{command}, args...)...)
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
// suggestDisplayManagerRestore scans for installed DMs and re-enables one
|
// suggestDisplayManagerRestore scans for installed DMs and re-enables one
|
||||||
func suggestDisplayManagerRestore(nonInteractive bool) {
|
func suggestDisplayManagerRestore(nonInteractive bool) {
|
||||||
knownDMs := []string{"gdm", "gdm3", "lightdm", "sddm", "lxdm", "xdm", "cosmic-greeter"}
|
knownDMs := []string{"gdm", "gdm3", "lightdm", "sddm", "lxdm", "xdm", "cosmic-greeter"}
|
||||||
@@ -439,10 +432,7 @@ func suggestDisplayManagerRestore(nonInteractive bool) {
|
|||||||
|
|
||||||
enableDM := func(dm string) {
|
enableDM := func(dm string) {
|
||||||
fmt.Printf(" Enabling %s...\n", dm)
|
fmt.Printf(" Enabling %s...\n", dm)
|
||||||
cmd := exec.Command("sudo", "systemctl", "enable", "--force", dm)
|
if err := privesc.Run(context.Background(), "", "systemctl", "enable", "--force", dm); err != nil {
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
if err := cmd.Run(); err != nil {
|
|
||||||
fmt.Printf(" ⚠ Failed to enable %s: %v\n", dm, err)
|
fmt.Printf(" ⚠ Failed to enable %s: %v\n", dm, err)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf(" ✓ %s enabled (will take effect on next boot).\n", dm)
|
fmt.Printf(" ✓ %s enabled (will take effect on next boot).\n", dm)
|
||||||
@@ -641,10 +631,7 @@ func syncGreeter(nonInteractive bool, forceAuth bool, local bool) error {
|
|||||||
|
|
||||||
if response != "n" && response != "no" {
|
if response != "n" && response != "no" {
|
||||||
fmt.Printf("\nAdding user to %s group...\n", greeterGroup)
|
fmt.Printf("\nAdding user to %s group...\n", greeterGroup)
|
||||||
addUserCmd := exec.Command("sudo", "usermod", "-aG", greeterGroup, currentUser.Username)
|
if err := privesc.Run(context.Background(), "", "usermod", "-aG", greeterGroup, currentUser.Username); err != nil {
|
||||||
addUserCmd.Stdout = os.Stdout
|
|
||||||
addUserCmd.Stderr = os.Stderr
|
|
||||||
if err := addUserCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to add user to %s group: %w", greeterGroup, err)
|
return fmt.Errorf("failed to add user to %s group: %w", greeterGroup, err)
|
||||||
}
|
}
|
||||||
fmt.Printf("✓ User added to %s group\n", greeterGroup)
|
fmt.Printf("✓ User added to %s group\n", greeterGroup)
|
||||||
@@ -869,22 +856,19 @@ func disableDisplayManager(dmName string) (bool, error) {
|
|||||||
actionTaken := false
|
actionTaken := false
|
||||||
|
|
||||||
if state.NeedsDisable {
|
if state.NeedsDisable {
|
||||||
var disableCmd *exec.Cmd
|
var action, actionVerb string
|
||||||
var actionVerb string
|
switch state.EnabledState {
|
||||||
|
case "static":
|
||||||
if state.EnabledState == "static" {
|
|
||||||
fmt.Printf(" Masking %s (static service cannot be disabled)...\n", dmName)
|
fmt.Printf(" Masking %s (static service cannot be disabled)...\n", dmName)
|
||||||
disableCmd = exec.Command("sudo", "systemctl", "mask", dmName)
|
action = "mask"
|
||||||
actionVerb = "masked"
|
actionVerb = "masked"
|
||||||
} else {
|
default:
|
||||||
fmt.Printf(" Disabling %s...\n", dmName)
|
fmt.Printf(" Disabling %s...\n", dmName)
|
||||||
disableCmd = exec.Command("sudo", "systemctl", "disable", dmName)
|
action = "disable"
|
||||||
actionVerb = "disabled"
|
actionVerb = "disabled"
|
||||||
}
|
}
|
||||||
|
|
||||||
disableCmd.Stdout = os.Stdout
|
if err := privesc.Run(context.Background(), "", "systemctl", action, dmName); err != nil {
|
||||||
disableCmd.Stderr = os.Stderr
|
|
||||||
if err := disableCmd.Run(); err != nil {
|
|
||||||
return actionTaken, fmt.Errorf("failed to disable/mask %s: %w", dmName, err)
|
return actionTaken, fmt.Errorf("failed to disable/mask %s: %w", dmName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -925,10 +909,7 @@ func ensureGreetdEnabled() error {
|
|||||||
|
|
||||||
if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
if state.EnabledState == "masked" || state.EnabledState == "masked-runtime" {
|
||||||
fmt.Println(" Unmasking greetd...")
|
fmt.Println(" Unmasking greetd...")
|
||||||
unmaskCmd := exec.Command("sudo", "systemctl", "unmask", "greetd")
|
if err := privesc.Run(context.Background(), "", "systemctl", "unmask", "greetd"); err != nil {
|
||||||
unmaskCmd.Stdout = os.Stdout
|
|
||||||
unmaskCmd.Stderr = os.Stderr
|
|
||||||
if err := unmaskCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to unmask greetd: %w", err)
|
return fmt.Errorf("failed to unmask greetd: %w", err)
|
||||||
}
|
}
|
||||||
fmt.Println(" ✓ Unmasked greetd")
|
fmt.Println(" ✓ Unmasked greetd")
|
||||||
@@ -940,10 +921,7 @@ func ensureGreetdEnabled() error {
|
|||||||
fmt.Println(" Enabling greetd service...")
|
fmt.Println(" Enabling greetd service...")
|
||||||
}
|
}
|
||||||
|
|
||||||
enableCmd := exec.Command("sudo", "systemctl", "enable", "--force", "greetd")
|
if err := privesc.Run(context.Background(), "", "systemctl", "enable", "--force", "greetd"); err != nil {
|
||||||
enableCmd.Stdout = os.Stdout
|
|
||||||
enableCmd.Stderr = os.Stderr
|
|
||||||
if err := enableCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -973,10 +951,7 @@ func ensureGraphicalTarget() error {
|
|||||||
currentTargetStr := strings.TrimSpace(string(currentTarget))
|
currentTargetStr := strings.TrimSpace(string(currentTarget))
|
||||||
if currentTargetStr != "graphical.target" {
|
if currentTargetStr != "graphical.target" {
|
||||||
fmt.Printf("\nSetting graphical.target as default (current: %s)...\n", currentTargetStr)
|
fmt.Printf("\nSetting graphical.target as default (current: %s)...\n", currentTargetStr)
|
||||||
setDefaultCmd := exec.Command("sudo", "systemctl", "set-default", "graphical.target")
|
if err := privesc.Run(context.Background(), "", "systemctl", "set-default", "graphical.target"); err != nil {
|
||||||
setDefaultCmd.Stdout = os.Stdout
|
|
||||||
setDefaultCmd.Stderr = os.Stderr
|
|
||||||
if err := setDefaultCmd.Run(); err != nil {
|
|
||||||
fmt.Println("⚠ Warning: Failed to set graphical.target as default")
|
fmt.Println("⚠ Warning: Failed to set graphical.target as default")
|
||||||
fmt.Println(" Greeter may not start on boot. Run manually:")
|
fmt.Println(" Greeter may not start on boot. Run manually:")
|
||||||
fmt.Println(" sudo systemctl set-default graphical.target")
|
fmt.Println(" sudo systemctl set-default graphical.target")
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ var setupCmd = &cobra.Command{
|
|||||||
Use: "setup",
|
Use: "setup",
|
||||||
Short: "Deploy DMS configurations",
|
Short: "Deploy DMS configurations",
|
||||||
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
Long: "Deploy compositor and terminal configurations with interactive prompts",
|
||||||
PersistentPreRunE: requireMutableSystemCommand,
|
PersistentPreRunE: preRunPrivileged,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if err := runSetup(); err != nil {
|
if err := runSetup(); err != nil {
|
||||||
log.Fatalf("Error during setup: %v", err)
|
log.Fatalf("Error during setup: %v", err)
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -269,3 +270,16 @@ func requireMutableSystemCommand(cmd *cobra.Command, _ []string) error {
|
|||||||
|
|
||||||
return fmt.Errorf("%s%s\nCommand: dms %s\nPolicy files:\n %s\n %s", reason, policy.Message, commandPath, cliPolicyPackagedPath, cliPolicyAdminPath)
|
return fmt.Errorf("%s%s\nCommand: dms %s\nPolicy files:\n %s\n %s", reason, policy.Message, commandPath, cliPolicyPackagedPath, cliPolicyAdminPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// preRunPrivileged combines the immutable-system check with a privesc tool
|
||||||
|
// selection prompt (shown only when multiple tools are available and the
|
||||||
|
// $DMS_PRIVESC env var isn't set).
|
||||||
|
func preRunPrivileged(cmd *cobra.Command, args []string) error {
|
||||||
|
if err := requireMutableSystemCommand(cmd, args); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := privesc.PromptCLI(os.Stdout, os.Stdin); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -292,7 +293,7 @@ func (a *ArchDistribution) InstallPrerequisites(ctx context.Context, sudoPasswor
|
|||||||
LogOutput: "Installing base-devel development tools",
|
LogOutput: "Installing base-devel development tools",
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, "pacman -S --needed --noconfirm base-devel")
|
cmd := privesc.ExecCommand(ctx, sudoPassword, "pacman -S --needed --noconfirm base-devel")
|
||||||
if err := a.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.10); err != nil {
|
if err := a.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.10); err != nil {
|
||||||
return fmt.Errorf("failed to install base-devel: %w", err)
|
return fmt.Errorf("failed to install base-devel: %w", err)
|
||||||
}
|
}
|
||||||
@@ -463,7 +464,7 @@ func (a *ArchDistribution) preinstallQuickshellGit(ctx context.Context, sudoPass
|
|||||||
CommandInfo: "sudo pacman -Rdd --noconfirm quickshell",
|
CommandInfo: "sudo pacman -Rdd --noconfirm quickshell",
|
||||||
LogOutput: "Removing stable quickshell so quickshell-git can be installed",
|
LogOutput: "Removing stable quickshell so quickshell-git can be installed",
|
||||||
}
|
}
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, "pacman -Rdd --noconfirm quickshell")
|
cmd := privesc.ExecCommand(ctx, sudoPassword, "pacman -Rdd --noconfirm quickshell")
|
||||||
if err := a.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.15, 0.18); err != nil {
|
if err := a.runWithProgress(cmd, progressChan, PhaseAURPackages, 0.15, 0.18); err != nil {
|
||||||
return fmt.Errorf("failed to remove stable quickshell: %w", err)
|
return fmt.Errorf("failed to remove stable quickshell: %w", err)
|
||||||
}
|
}
|
||||||
@@ -501,7 +502,7 @@ func (a *ArchDistribution) installSystemPackages(ctx context.Context, packages [
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return a.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
return a.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -779,7 +780,7 @@ func (a *ArchDistribution) installSingleAURPackageInternal(ctx context.Context,
|
|||||||
installArgs := []string{"pacman", "-U", "--noconfirm"}
|
installArgs := []string{"pacman", "-U", "--noconfirm"}
|
||||||
installArgs = append(installArgs, files...)
|
installArgs = append(installArgs, files...)
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(installArgs, " "))
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(installArgs, " "))
|
||||||
|
|
||||||
fileNames := make([]string, len(files))
|
fileNames := make([]string, len(files))
|
||||||
for i, f := range files {
|
for i, f := range files {
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/version"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -55,27 +56,6 @@ func (b *BaseDistribution) logError(message string, err error) {
|
|||||||
b.log(errorMsg)
|
b.log(errorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
// escapeSingleQuotes escapes single quotes in a string for safe use in bash single-quoted strings.
|
|
||||||
// It replaces each ' with '\” which closes the quote, adds an escaped quote, and reopens the quote.
|
|
||||||
// This prevents shell injection and syntax errors when passwords contain single quotes or apostrophes.
|
|
||||||
func escapeSingleQuotes(s string) string {
|
|
||||||
return strings.ReplaceAll(s, "'", "'\\''")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeSudoCommand creates a command string that safely passes password to sudo.
|
|
||||||
// This helper escapes special characters in the password to prevent shell injection
|
|
||||||
// and syntax errors when passwords contain single quotes, apostrophes, or other special chars.
|
|
||||||
func MakeSudoCommand(sudoPassword string, command string) string {
|
|
||||||
return fmt.Sprintf("echo '%s' | sudo -S %s", escapeSingleQuotes(sudoPassword), command)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ExecSudoCommand creates an exec.Cmd that runs a command with sudo using the provided password.
|
|
||||||
// The password is properly escaped to prevent shell injection and syntax errors.
|
|
||||||
func ExecSudoCommand(ctx context.Context, sudoPassword string, command string) *exec.Cmd {
|
|
||||||
cmdStr := MakeSudoCommand(sudoPassword, command)
|
|
||||||
return exec.CommandContext(ctx, "bash", "-c", cmdStr)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (b *BaseDistribution) detectCommand(name, description string) deps.Dependency {
|
func (b *BaseDistribution) detectCommand(name, description string) deps.Dependency {
|
||||||
status := deps.StatusMissing
|
status := deps.StatusMissing
|
||||||
if b.commandExists(name) {
|
if b.commandExists(name) {
|
||||||
@@ -710,7 +690,7 @@ func (b *BaseDistribution) installDMSBinary(ctx context.Context, sudoPassword st
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Install to /usr/local/bin
|
// Install to /usr/local/bin
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword,
|
installCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("cp %s /usr/local/bin/dms", binaryPath))
|
fmt.Sprintf("cp %s /usr/local/bin/dms", binaryPath))
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to install DMS binary: %w", err)
|
return fmt.Errorf("failed to install DMS binary: %w", err)
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -182,7 +183,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
LogOutput: "Updating APT package lists",
|
LogOutput: "Updating APT package lists",
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
|
updateCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get update")
|
||||||
if err := d.runWithProgress(updateCmd, progressChan, PhasePrerequisites, 0.06, 0.07); err != nil {
|
if err := d.runWithProgress(updateCmd, progressChan, PhasePrerequisites, 0.06, 0.07); err != nil {
|
||||||
return fmt.Errorf("failed to update package lists: %w", err)
|
return fmt.Errorf("failed to update package lists: %w", err)
|
||||||
}
|
}
|
||||||
@@ -199,7 +200,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
|
|
||||||
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
|
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
|
||||||
if err := checkCmd.Run(); err != nil {
|
if err := checkCmd.Run(); err != nil {
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential")
|
cmd := privesc.ExecCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y build-essential")
|
||||||
if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
|
if err := d.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
|
||||||
return fmt.Errorf("failed to install build-essential: %w", err)
|
return fmt.Errorf("failed to install build-essential: %w", err)
|
||||||
}
|
}
|
||||||
@@ -215,7 +216,7 @@ func (d *DebianDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
LogOutput: "Installing additional development tools",
|
LogOutput: "Installing additional development tools",
|
||||||
}
|
}
|
||||||
|
|
||||||
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
devToolsCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
"DEBIAN_FRONTEND=noninteractive apt-get install -y curl wget git cmake ninja-build pkg-config gnupg libxcb-cursor-dev libglib2.0-dev libpolkit-agent-1-dev libjpeg-dev libpugixml-dev")
|
||||||
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
if err := d.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||||
return fmt.Errorf("failed to install development tools: %w", err)
|
return fmt.Errorf("failed to install development tools: %w", err)
|
||||||
@@ -441,7 +442,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
keyringPath := fmt.Sprintf("/etc/apt/keyrings/%s.gpg", repoName)
|
keyringPath := fmt.Sprintf("/etc/apt/keyrings/%s.gpg", repoName)
|
||||||
|
|
||||||
// Create keyrings directory if it doesn't exist
|
// Create keyrings directory if it doesn't exist
|
||||||
mkdirCmd := ExecSudoCommand(ctx, sudoPassword, "mkdir -p /etc/apt/keyrings")
|
mkdirCmd := privesc.ExecCommand(ctx, sudoPassword, "mkdir -p /etc/apt/keyrings")
|
||||||
if err := mkdirCmd.Run(); err != nil {
|
if err := mkdirCmd.Run(); err != nil {
|
||||||
d.log(fmt.Sprintf("Warning: failed to create keyrings directory: %v", err))
|
d.log(fmt.Sprintf("Warning: failed to create keyrings directory: %v", err))
|
||||||
}
|
}
|
||||||
@@ -455,7 +456,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
}
|
}
|
||||||
|
|
||||||
keyCmd := fmt.Sprintf("bash -c 'rm -f %s && curl -fsSL %s/Release.key | gpg --batch --dearmor -o %s'", keyringPath, baseURL, keyringPath)
|
keyCmd := fmt.Sprintf("bash -c 'rm -f %s && curl -fsSL %s/Release.key | gpg --batch --dearmor -o %s'", keyringPath, baseURL, keyringPath)
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, keyCmd)
|
cmd := privesc.ExecCommand(ctx, sudoPassword, keyCmd)
|
||||||
if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
|
if err := d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.18, 0.20); err != nil {
|
||||||
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
return fmt.Errorf("failed to add OBS GPG key for %s: %w", pkg.RepoURL, err)
|
||||||
}
|
}
|
||||||
@@ -471,7 +472,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
CommandInfo: fmt.Sprintf("echo '%s' | sudo tee %s", repoLine, listFile),
|
CommandInfo: fmt.Sprintf("echo '%s' | sudo tee %s", repoLine, listFile),
|
||||||
}
|
}
|
||||||
|
|
||||||
addRepoCmd := ExecSudoCommand(ctx, sudoPassword,
|
addRepoCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("bash -c \"echo '%s' | tee %s\"", repoLine, listFile))
|
fmt.Sprintf("bash -c \"echo '%s' | tee %s\"", repoLine, listFile))
|
||||||
if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
if err := d.runWithProgress(addRepoCmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||||
return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
|
return fmt.Errorf("failed to add OBS repo %s: %w", pkg.RepoURL, err)
|
||||||
@@ -491,7 +492,7 @@ func (d *DebianDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Packa
|
|||||||
CommandInfo: "sudo apt-get update",
|
CommandInfo: "sudo apt-get update",
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
|
updateCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get update")
|
||||||
if err := d.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
if err := d.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
||||||
return fmt.Errorf("failed to update package lists after adding OBS repos: %w", err)
|
return fmt.Errorf("failed to update package lists after adding OBS repos: %w", err)
|
||||||
}
|
}
|
||||||
@@ -537,7 +538,7 @@ func (d *DebianDistribution) installAPTPackages(ctx context.Context, packages []
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, startProgress, endProgress)
|
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, startProgress, endProgress)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -625,7 +626,7 @@ func (d *DebianDistribution) installBuildDependencies(ctx context.Context, manua
|
|||||||
args := []string{"apt-get", "install", "-y"}
|
args := []string{"apt-get", "install", "-y"}
|
||||||
args = append(args, depList...)
|
args = append(args, depList...)
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.80, 0.82)
|
return d.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.80, 0.82)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -643,7 +644,7 @@ func (d *DebianDistribution) installRust(ctx context.Context, sudoPassword strin
|
|||||||
CommandInfo: "sudo apt-get install rustup",
|
CommandInfo: "sudo apt-get install rustup",
|
||||||
}
|
}
|
||||||
|
|
||||||
rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y rustup")
|
rustupInstallCmd := privesc.ExecCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y rustup")
|
||||||
if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
if err := d.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
||||||
return fmt.Errorf("failed to install rustup: %w", err)
|
return fmt.Errorf("failed to install rustup: %w", err)
|
||||||
}
|
}
|
||||||
@@ -682,7 +683,7 @@ func (d *DebianDistribution) installGo(ctx context.Context, sudoPassword string,
|
|||||||
CommandInfo: "sudo apt-get install golang-go",
|
CommandInfo: "sudo apt-get install golang-go",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y golang-go")
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, "DEBIAN_FRONTEND=noninteractive apt-get install -y golang-go")
|
||||||
return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90)
|
return d.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.87, 0.90)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -254,7 +255,7 @@ func (f *FedoraDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
|
|
||||||
args := []string{"dnf", "install", "-y"}
|
args := []string{"dnf", "install", "-y"}
|
||||||
args = append(args, missingPkgs...)
|
args = append(args, missingPkgs...)
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
f.logError("failed to install prerequisites", err)
|
f.logError("failed to install prerequisites", err)
|
||||||
@@ -437,7 +438,7 @@ func (f *FedoraDistribution) enableCOPRRepos(ctx context.Context, coprPkgs []Pac
|
|||||||
CommandInfo: fmt.Sprintf("sudo dnf copr enable -y %s", pkg.RepoURL),
|
CommandInfo: fmt.Sprintf("sudo dnf copr enable -y %s", pkg.RepoURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword,
|
cmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("dnf copr enable -y %s 2>&1", pkg.RepoURL))
|
fmt.Sprintf("dnf copr enable -y %s 2>&1", pkg.RepoURL))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -461,7 +462,7 @@ func (f *FedoraDistribution) enableCOPRRepos(ctx context.Context, coprPkgs []Pac
|
|||||||
CommandInfo: fmt.Sprintf("echo \"priority=1\" | sudo tee -a %s", repoFile),
|
CommandInfo: fmt.Sprintf("echo \"priority=1\" | sudo tee -a %s", repoFile),
|
||||||
}
|
}
|
||||||
|
|
||||||
priorityCmd := ExecSudoCommand(ctx, sudoPassword,
|
priorityCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("bash -c 'echo \"priority=1\" | tee -a %s'", repoFile))
|
fmt.Sprintf("bash -c 'echo \"priority=1\" | tee -a %s'", repoFile))
|
||||||
priorityOutput, err := priorityCmd.CombinedOutput()
|
priorityOutput, err := priorityCmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -537,7 +538,7 @@ func (f *FedoraDistribution) installDNFGroups(ctx context.Context, packages []st
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return f.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
return f.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var GentooGlobalUseFlags = []string{
|
var GentooGlobalUseFlags = []string{
|
||||||
@@ -201,9 +202,9 @@ func (g *GentooDistribution) setGlobalUseFlags(ctx context.Context, sudoPassword
|
|||||||
|
|
||||||
var cmd *exec.Cmd
|
var cmd *exec.Cmd
|
||||||
if hasUse {
|
if hasUse {
|
||||||
cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("sed -i 's/^USE=\"\\(.*\\)\"/USE=\"\\1 %s\"/' /etc/portage/make.conf", useFlags))
|
cmd = privesc.ExecCommand(ctx, sudoPassword, fmt.Sprintf("sed -i 's/^USE=\"\\(.*\\)\"/USE=\"\\1 %s\"/' /etc/portage/make.conf", useFlags))
|
||||||
} else {
|
} else {
|
||||||
cmd = ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("bash -c \"echo 'USE=\\\"%s\\\"' >> /etc/portage/make.conf\"", useFlags))
|
cmd = privesc.ExecCommand(ctx, sudoPassword, fmt.Sprintf("bash -c \"echo 'USE=\\\"%s\\\"' >> /etc/portage/make.conf\"", useFlags))
|
||||||
}
|
}
|
||||||
|
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
@@ -281,7 +282,7 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
LogOutput: "Syncing Portage tree with emerge --sync",
|
LogOutput: "Syncing Portage tree with emerge --sync",
|
||||||
}
|
}
|
||||||
|
|
||||||
syncCmd := ExecSudoCommand(ctx, sudoPassword, "emerge --sync --quiet")
|
syncCmd := privesc.ExecCommand(ctx, sudoPassword, "emerge --sync --quiet")
|
||||||
syncOutput, syncErr := syncCmd.CombinedOutput()
|
syncOutput, syncErr := syncCmd.CombinedOutput()
|
||||||
if syncErr != nil {
|
if syncErr != nil {
|
||||||
g.log(fmt.Sprintf("emerge --sync output: %s", string(syncOutput)))
|
g.log(fmt.Sprintf("emerge --sync output: %s", string(syncOutput)))
|
||||||
@@ -302,7 +303,7 @@ func (g *GentooDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
|
|
||||||
args := []string{"emerge", "--ask=n", "--quiet"}
|
args := []string{"emerge", "--ask=n", "--quiet"}
|
||||||
args = append(args, missingPkgs...)
|
args = append(args, missingPkgs...)
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
g.logError("failed to install prerequisites", err)
|
g.logError("failed to install prerequisites", err)
|
||||||
@@ -503,14 +504,14 @@ func (g *GentooDistribution) installPortagePackages(ctx context.Context, package
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return g.runWithProgressTimeout(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60, 0)
|
return g.runWithProgressTimeout(cmd, progressChan, PhaseSystemPackages, 0.40, 0.60, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (g *GentooDistribution) setPackageUseFlags(ctx context.Context, packageName, useFlags, sudoPassword string) error {
|
func (g *GentooDistribution) setPackageUseFlags(ctx context.Context, packageName, useFlags, sudoPassword string) error {
|
||||||
packageUseDir := "/etc/portage/package.use"
|
packageUseDir := "/etc/portage/package.use"
|
||||||
|
|
||||||
mkdirCmd := ExecSudoCommand(ctx, sudoPassword,
|
mkdirCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("mkdir -p %s", packageUseDir))
|
fmt.Sprintf("mkdir -p %s", packageUseDir))
|
||||||
if output, err := mkdirCmd.CombinedOutput(); err != nil {
|
if output, err := mkdirCmd.CombinedOutput(); err != nil {
|
||||||
g.log(fmt.Sprintf("mkdir output: %s", string(output)))
|
g.log(fmt.Sprintf("mkdir output: %s", string(output)))
|
||||||
@@ -524,7 +525,7 @@ func (g *GentooDistribution) setPackageUseFlags(ctx context.Context, packageName
|
|||||||
if checkExistingCmd.Run() == nil {
|
if checkExistingCmd.Run() == nil {
|
||||||
g.log(fmt.Sprintf("Updating USE flags for %s from existing entry", packageName))
|
g.log(fmt.Sprintf("Updating USE flags for %s from existing entry", packageName))
|
||||||
escapedPkg := strings.ReplaceAll(packageName, "/", "\\/")
|
escapedPkg := strings.ReplaceAll(packageName, "/", "\\/")
|
||||||
replaceCmd := ExecSudoCommand(ctx, sudoPassword,
|
replaceCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("sed -i '/^%s /d' %s/danklinux; exit_code=$?; exit $exit_code", escapedPkg, packageUseDir))
|
fmt.Sprintf("sed -i '/^%s /d' %s/danklinux; exit_code=$?; exit $exit_code", escapedPkg, packageUseDir))
|
||||||
if output, err := replaceCmd.CombinedOutput(); err != nil {
|
if output, err := replaceCmd.CombinedOutput(); err != nil {
|
||||||
g.log(fmt.Sprintf("sed delete output: %s", string(output)))
|
g.log(fmt.Sprintf("sed delete output: %s", string(output)))
|
||||||
@@ -532,7 +533,7 @@ func (g *GentooDistribution) setPackageUseFlags(ctx context.Context, packageName
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendCmd := ExecSudoCommand(ctx, sudoPassword,
|
appendCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("bash -c \"echo '%s' >> %s/danklinux\"", useFlagLine, packageUseDir))
|
fmt.Sprintf("bash -c \"echo '%s' >> %s/danklinux\"", useFlagLine, packageUseDir))
|
||||||
|
|
||||||
output, err := appendCmd.CombinedOutput()
|
output, err := appendCmd.CombinedOutput()
|
||||||
@@ -557,7 +558,7 @@ func (g *GentooDistribution) syncGURURepo(ctx context.Context, sudoPassword stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Enable GURU repository
|
// Enable GURU repository
|
||||||
enableCmd := ExecSudoCommand(ctx, sudoPassword,
|
enableCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"eselect repository enable guru 2>&1; exit_code=$?; exit $exit_code")
|
"eselect repository enable guru 2>&1; exit_code=$?; exit $exit_code")
|
||||||
output, err := enableCmd.CombinedOutput()
|
output, err := enableCmd.CombinedOutput()
|
||||||
|
|
||||||
@@ -589,7 +590,7 @@ func (g *GentooDistribution) syncGURURepo(ctx context.Context, sudoPassword stri
|
|||||||
LogOutput: "Syncing GURU repository",
|
LogOutput: "Syncing GURU repository",
|
||||||
}
|
}
|
||||||
|
|
||||||
syncCmd := ExecSudoCommand(ctx, sudoPassword,
|
syncCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"emaint sync --repo guru 2>&1; exit_code=$?; exit $exit_code")
|
"emaint sync --repo guru 2>&1; exit_code=$?; exit $exit_code")
|
||||||
syncOutput, syncErr := syncCmd.CombinedOutput()
|
syncOutput, syncErr := syncCmd.CombinedOutput()
|
||||||
|
|
||||||
@@ -622,7 +623,7 @@ func (g *GentooDistribution) setPackageAcceptKeywords(ctx context.Context, packa
|
|||||||
|
|
||||||
acceptKeywordsDir := "/etc/portage/package.accept_keywords"
|
acceptKeywordsDir := "/etc/portage/package.accept_keywords"
|
||||||
|
|
||||||
mkdirCmd := ExecSudoCommand(ctx, sudoPassword,
|
mkdirCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("mkdir -p %s", acceptKeywordsDir))
|
fmt.Sprintf("mkdir -p %s", acceptKeywordsDir))
|
||||||
if output, err := mkdirCmd.CombinedOutput(); err != nil {
|
if output, err := mkdirCmd.CombinedOutput(); err != nil {
|
||||||
g.log(fmt.Sprintf("mkdir output: %s", string(output)))
|
g.log(fmt.Sprintf("mkdir output: %s", string(output)))
|
||||||
@@ -636,7 +637,7 @@ func (g *GentooDistribution) setPackageAcceptKeywords(ctx context.Context, packa
|
|||||||
if checkExistingCmd.Run() == nil {
|
if checkExistingCmd.Run() == nil {
|
||||||
g.log(fmt.Sprintf("Updating accept keywords for %s from existing entry", packageName))
|
g.log(fmt.Sprintf("Updating accept keywords for %s from existing entry", packageName))
|
||||||
escapedPkg := strings.ReplaceAll(packageName, "/", "\\/")
|
escapedPkg := strings.ReplaceAll(packageName, "/", "\\/")
|
||||||
replaceCmd := ExecSudoCommand(ctx, sudoPassword,
|
replaceCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("sed -i '/^%s /d' %s/danklinux; exit_code=$?; exit $exit_code", escapedPkg, acceptKeywordsDir))
|
fmt.Sprintf("sed -i '/^%s /d' %s/danklinux; exit_code=$?; exit $exit_code", escapedPkg, acceptKeywordsDir))
|
||||||
if output, err := replaceCmd.CombinedOutput(); err != nil {
|
if output, err := replaceCmd.CombinedOutput(); err != nil {
|
||||||
g.log(fmt.Sprintf("sed delete output: %s", string(output)))
|
g.log(fmt.Sprintf("sed delete output: %s", string(output)))
|
||||||
@@ -644,7 +645,7 @@ func (g *GentooDistribution) setPackageAcceptKeywords(ctx context.Context, packa
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendCmd := ExecSudoCommand(ctx, sudoPassword,
|
appendCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("bash -c \"echo '%s' >> %s/danklinux\"", keywordLine, acceptKeywordsDir))
|
fmt.Sprintf("bash -c \"echo '%s' >> %s/danklinux\"", keywordLine, acceptKeywordsDir))
|
||||||
|
|
||||||
output, err := appendCmd.CombinedOutput()
|
output, err := appendCmd.CombinedOutput()
|
||||||
@@ -695,6 +696,6 @@ func (g *GentooDistribution) installGURUPackages(ctx context.Context, packages [
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return g.runWithProgressTimeout(cmd, progressChan, PhaseAURPackages, 0.70, 0.85, 0)
|
return g.runWithProgressTimeout(cmd, progressChan, PhaseAURPackages, 0.70, 0.85, 0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ManualPackageInstaller provides methods for installing packages from source
|
// ManualPackageInstaller provides methods for installing packages from source
|
||||||
@@ -143,7 +144,7 @@ func (m *ManualPackageInstaller) installDgop(ctx context.Context, sudoPassword s
|
|||||||
CommandInfo: "sudo make install",
|
CommandInfo: "sudo make install",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "make install")
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, "make install")
|
||||||
installCmd.Dir = tmpDir
|
installCmd.Dir = tmpDir
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
m.logError("failed to install dgop", err)
|
m.logError("failed to install dgop", err)
|
||||||
@@ -213,7 +214,7 @@ func (m *ManualPackageInstaller) installNiri(ctx context.Context, sudoPassword s
|
|||||||
CommandInfo: "dpkg -i niri.deb",
|
CommandInfo: "dpkg -i niri.deb",
|
||||||
}
|
}
|
||||||
|
|
||||||
installDebCmd := ExecSudoCommand(ctx, sudoPassword,
|
installDebCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("dpkg -i %s/target/debian/niri_*.deb", buildDir))
|
fmt.Sprintf("dpkg -i %s/target/debian/niri_*.deb", buildDir))
|
||||||
|
|
||||||
output, err := installDebCmd.CombinedOutput()
|
output, err := installDebCmd.CombinedOutput()
|
||||||
@@ -324,7 +325,7 @@ func (m *ManualPackageInstaller) installQuickshell(ctx context.Context, variant
|
|||||||
CommandInfo: "sudo cmake --install build",
|
CommandInfo: "sudo cmake --install build",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, "cmake --install build")
|
||||||
installCmd.Dir = tmpDir
|
installCmd.Dir = tmpDir
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to install quickshell: %w", err)
|
return fmt.Errorf("failed to install quickshell: %w", err)
|
||||||
@@ -387,7 +388,7 @@ func (m *ManualPackageInstaller) installHyprland(ctx context.Context, sudoPasswo
|
|||||||
CommandInfo: "sudo make install",
|
CommandInfo: "sudo make install",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "make install")
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, "make install")
|
||||||
installCmd.Dir = tmpDir
|
installCmd.Dir = tmpDir
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to install Hyprland: %w", err)
|
return fmt.Errorf("failed to install Hyprland: %w", err)
|
||||||
@@ -453,7 +454,7 @@ func (m *ManualPackageInstaller) installGhostty(ctx context.Context, sudoPasswor
|
|||||||
CommandInfo: "sudo cp zig-out/bin/ghostty /usr/local/bin/",
|
CommandInfo: "sudo cp zig-out/bin/ghostty /usr/local/bin/",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword,
|
installCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("cp %s/zig-out/bin/ghostty /usr/local/bin/", tmpDir))
|
fmt.Sprintf("cp %s/zig-out/bin/ghostty /usr/local/bin/", tmpDir))
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to install Ghostty: %w", err)
|
return fmt.Errorf("failed to install Ghostty: %w", err)
|
||||||
@@ -492,16 +493,11 @@ func (m *ManualPackageInstaller) installMatugen(ctx context.Context, sudoPasswor
|
|||||||
CommandInfo: fmt.Sprintf("sudo cp %s %s", sourcePath, targetPath),
|
CommandInfo: fmt.Sprintf("sudo cp %s %s", sourcePath, targetPath),
|
||||||
}
|
}
|
||||||
|
|
||||||
copyCmd := exec.CommandContext(ctx, "sudo", "-S", "cp", sourcePath, targetPath)
|
if err := privesc.Run(ctx, sudoPassword, "cp", sourcePath, targetPath); err != nil {
|
||||||
copyCmd.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
||||||
if err := copyCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy matugen to /usr/local/bin: %w", err)
|
return fmt.Errorf("failed to copy matugen to /usr/local/bin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make it executable
|
if err := privesc.Run(ctx, sudoPassword, "chmod", "+x", targetPath); err != nil {
|
||||||
chmodCmd := exec.CommandContext(ctx, "sudo", "-S", "chmod", "+x", targetPath)
|
|
||||||
chmodCmd.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
||||||
if err := chmodCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to make matugen executable: %w", err)
|
return fmt.Errorf("failed to make matugen executable: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,15 +642,11 @@ func (m *ManualPackageInstaller) installXwaylandSatellite(ctx context.Context, s
|
|||||||
CommandInfo: fmt.Sprintf("sudo cp %s %s", sourcePath, targetPath),
|
CommandInfo: fmt.Sprintf("sudo cp %s %s", sourcePath, targetPath),
|
||||||
}
|
}
|
||||||
|
|
||||||
copyCmd := exec.CommandContext(ctx, "sudo", "-S", "cp", sourcePath, targetPath)
|
if err := privesc.Run(ctx, sudoPassword, "cp", sourcePath, targetPath); err != nil {
|
||||||
copyCmd.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
||||||
if err := copyCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to copy xwayland-satellite to /usr/local/bin: %w", err)
|
return fmt.Errorf("failed to copy xwayland-satellite to /usr/local/bin: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
chmodCmd := exec.CommandContext(ctx, "sudo", "-S", "chmod", "+x", targetPath)
|
if err := privesc.Run(ctx, sudoPassword, "chmod", "+x", targetPath); err != nil {
|
||||||
chmodCmd.Stdin = strings.NewReader(sudoPassword + "\n")
|
|
||||||
if err := chmodCmd.Run(); err != nil {
|
|
||||||
return fmt.Errorf("failed to make xwayland-satellite executable: %w", err)
|
return fmt.Errorf("failed to make xwayland-satellite executable: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -250,7 +251,7 @@ func (o *OpenSUSEDistribution) InstallPrerequisites(ctx context.Context, sudoPas
|
|||||||
|
|
||||||
args := []string{"zypper", "install", "-y"}
|
args := []string{"zypper", "install", "-y"}
|
||||||
args = append(args, missingPkgs...)
|
args = append(args, missingPkgs...)
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
o.logError("failed to install prerequisites", err)
|
o.logError("failed to install prerequisites", err)
|
||||||
@@ -486,7 +487,7 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
|
|||||||
CommandInfo: fmt.Sprintf("sudo zypper addrepo %s", repoURL),
|
CommandInfo: fmt.Sprintf("sudo zypper addrepo %s", repoURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword,
|
cmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("zypper addrepo -f %s", repoURL))
|
fmt.Sprintf("zypper addrepo -f %s", repoURL))
|
||||||
if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
if err := o.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||||
o.log(fmt.Sprintf("OBS repo %s add failed (may already exist): %v", pkg.RepoURL, err))
|
o.log(fmt.Sprintf("OBS repo %s add failed (may already exist): %v", pkg.RepoURL, err))
|
||||||
@@ -507,7 +508,7 @@ func (o *OpenSUSEDistribution) enableOBSRepos(ctx context.Context, obsPkgs []Pac
|
|||||||
CommandInfo: "sudo zypper --gpg-auto-import-keys refresh",
|
CommandInfo: "sudo zypper --gpg-auto-import-keys refresh",
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshCmd := ExecSudoCommand(ctx, sudoPassword, "zypper --gpg-auto-import-keys refresh")
|
refreshCmd := privesc.ExecCommand(ctx, sudoPassword, "zypper --gpg-auto-import-keys refresh")
|
||||||
if err := o.runWithProgress(refreshCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
if err := o.runWithProgress(refreshCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
||||||
return fmt.Errorf("failed to refresh repositories: %w", err)
|
return fmt.Errorf("failed to refresh repositories: %w", err)
|
||||||
}
|
}
|
||||||
@@ -588,7 +589,7 @@ func (o *OpenSUSEDistribution) disableInstallMediaRepos(ctx context.Context, sud
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, alias := range aliases {
|
for _, alias := range aliases {
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, fmt.Sprintf("zypper modifyrepo -d '%s'", escapeSingleQuotes(alias)))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, fmt.Sprintf("zypper modifyrepo -d '%s'", privesc.EscapeSingleQuotes(alias)))
|
||||||
repoOutput, err := cmd.CombinedOutput()
|
repoOutput, err := cmd.CombinedOutput()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
o.log(fmt.Sprintf("Failed to disable install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
|
o.log(fmt.Sprintf("Failed to disable install media repo %s: %s", alias, strings.TrimSpace(string(repoOutput))))
|
||||||
@@ -646,7 +647,7 @@ func (o *OpenSUSEDistribution) installZypperPackages(ctx context.Context, packag
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return o.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
return o.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -774,7 +775,7 @@ func (o *OpenSUSEDistribution) installQuickshell(ctx context.Context, variant de
|
|||||||
CommandInfo: "sudo cmake --install build",
|
CommandInfo: "sudo cmake --install build",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "cmake --install build")
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, "cmake --install build")
|
||||||
installCmd.Dir = tmpDir
|
installCmd.Dir = tmpDir
|
||||||
if err := installCmd.Run(); err != nil {
|
if err := installCmd.Run(); err != nil {
|
||||||
return fmt.Errorf("failed to install quickshell: %w", err)
|
return fmt.Errorf("failed to install quickshell: %w", err)
|
||||||
@@ -798,7 +799,7 @@ func (o *OpenSUSEDistribution) installRust(ctx context.Context, sudoPassword str
|
|||||||
CommandInfo: "sudo zypper install rustup",
|
CommandInfo: "sudo zypper install rustup",
|
||||||
}
|
}
|
||||||
|
|
||||||
rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "zypper install -y rustup")
|
rustupInstallCmd := privesc.ExecCommand(ctx, sudoPassword, "zypper install -y rustup")
|
||||||
if err := o.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
if err := o.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
||||||
return fmt.Errorf("failed to install rustup: %w", err)
|
return fmt.Errorf("failed to install rustup: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -177,7 +178,7 @@ func (u *UbuntuDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
LogOutput: "Updating APT package lists",
|
LogOutput: "Updating APT package lists",
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
|
updateCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get update")
|
||||||
if err := u.runWithProgress(updateCmd, progressChan, PhasePrerequisites, 0.06, 0.07); err != nil {
|
if err := u.runWithProgress(updateCmd, progressChan, PhasePrerequisites, 0.06, 0.07); err != nil {
|
||||||
return fmt.Errorf("failed to update package lists: %w", err)
|
return fmt.Errorf("failed to update package lists: %w", err)
|
||||||
}
|
}
|
||||||
@@ -195,7 +196,7 @@ func (u *UbuntuDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
|
checkCmd := exec.CommandContext(ctx, "dpkg", "-l", "build-essential")
|
||||||
if err := checkCmd.Run(); err != nil {
|
if err := checkCmd.Run(); err != nil {
|
||||||
// Not installed, install it
|
// Not installed, install it
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y build-essential")
|
cmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y build-essential")
|
||||||
if err := u.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
|
if err := u.runWithProgress(cmd, progressChan, PhasePrerequisites, 0.08, 0.09); err != nil {
|
||||||
return fmt.Errorf("failed to install build-essential: %w", err)
|
return fmt.Errorf("failed to install build-essential: %w", err)
|
||||||
}
|
}
|
||||||
@@ -211,7 +212,7 @@ func (u *UbuntuDistribution) InstallPrerequisites(ctx context.Context, sudoPassw
|
|||||||
LogOutput: "Installing additional development tools",
|
LogOutput: "Installing additional development tools",
|
||||||
}
|
}
|
||||||
|
|
||||||
devToolsCmd := ExecSudoCommand(ctx, sudoPassword,
|
devToolsCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"apt-get install -y curl wget git cmake ninja-build pkg-config libglib2.0-dev libpolkit-agent-1-dev")
|
"apt-get install -y curl wget git cmake ninja-build pkg-config libglib2.0-dev libpolkit-agent-1-dev")
|
||||||
if err := u.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
if err := u.runWithProgress(devToolsCmd, progressChan, PhasePrerequisites, 0.10, 0.12); err != nil {
|
||||||
return fmt.Errorf("failed to install development tools: %w", err)
|
return fmt.Errorf("failed to install development tools: %w", err)
|
||||||
@@ -398,7 +399,7 @@ func (u *UbuntuDistribution) extractPackageNames(packages []PackageMapping) []st
|
|||||||
func (u *UbuntuDistribution) enablePPARepos(ctx context.Context, ppaPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
func (u *UbuntuDistribution) enablePPARepos(ctx context.Context, ppaPkgs []PackageMapping, sudoPassword string, progressChan chan<- InstallProgressMsg) error {
|
||||||
enabledRepos := make(map[string]bool)
|
enabledRepos := make(map[string]bool)
|
||||||
|
|
||||||
installPPACmd := ExecSudoCommand(ctx, sudoPassword,
|
installPPACmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"apt-get install -y software-properties-common")
|
"apt-get install -y software-properties-common")
|
||||||
if err := u.runWithProgress(installPPACmd, progressChan, PhaseSystemPackages, 0.15, 0.17); err != nil {
|
if err := u.runWithProgress(installPPACmd, progressChan, PhaseSystemPackages, 0.15, 0.17); err != nil {
|
||||||
return fmt.Errorf("failed to install software-properties-common: %w", err)
|
return fmt.Errorf("failed to install software-properties-common: %w", err)
|
||||||
@@ -416,7 +417,7 @@ func (u *UbuntuDistribution) enablePPARepos(ctx context.Context, ppaPkgs []Packa
|
|||||||
CommandInfo: fmt.Sprintf("sudo add-apt-repository -y %s", pkg.RepoURL),
|
CommandInfo: fmt.Sprintf("sudo add-apt-repository -y %s", pkg.RepoURL),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword,
|
cmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf("add-apt-repository -y %s", pkg.RepoURL))
|
fmt.Sprintf("add-apt-repository -y %s", pkg.RepoURL))
|
||||||
if err := u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
if err := u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.20, 0.22); err != nil {
|
||||||
u.logError(fmt.Sprintf("failed to enable PPA repo %s", pkg.RepoURL), err)
|
u.logError(fmt.Sprintf("failed to enable PPA repo %s", pkg.RepoURL), err)
|
||||||
@@ -437,7 +438,7 @@ func (u *UbuntuDistribution) enablePPARepos(ctx context.Context, ppaPkgs []Packa
|
|||||||
CommandInfo: "sudo apt-get update",
|
CommandInfo: "sudo apt-get update",
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
|
updateCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get update")
|
||||||
if err := u.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
if err := u.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.25, 0.27); err != nil {
|
||||||
return fmt.Errorf("failed to update package lists after adding PPAs: %w", err)
|
return fmt.Errorf("failed to update package lists after adding PPAs: %w", err)
|
||||||
}
|
}
|
||||||
@@ -504,7 +505,7 @@ func (u *UbuntuDistribution) installAPTGroups(ctx context.Context, packages []st
|
|||||||
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
CommandInfo: fmt.Sprintf("sudo %s", strings.Join(args, " ")),
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return u.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
return u.runWithProgress(cmd, progressChan, phase, groupStart, groupEnd)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -591,7 +592,7 @@ func (u *UbuntuDistribution) installBuildDependencies(ctx context.Context, manua
|
|||||||
args := []string{"apt-get", "install", "-y"}
|
args := []string{"apt-get", "install", "-y"}
|
||||||
args = append(args, depList...)
|
args = append(args, depList...)
|
||||||
|
|
||||||
cmd := ExecSudoCommand(ctx, sudoPassword, strings.Join(args, " "))
|
cmd := privesc.ExecCommand(ctx, sudoPassword, strings.Join(args, " "))
|
||||||
return u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.80, 0.82)
|
return u.runWithProgress(cmd, progressChan, PhaseSystemPackages, 0.80, 0.82)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -609,7 +610,7 @@ func (u *UbuntuDistribution) installRust(ctx context.Context, sudoPassword strin
|
|||||||
CommandInfo: "sudo apt-get install rustup",
|
CommandInfo: "sudo apt-get install rustup",
|
||||||
}
|
}
|
||||||
|
|
||||||
rustupInstallCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y rustup")
|
rustupInstallCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y rustup")
|
||||||
if err := u.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
if err := u.runWithProgress(rustupInstallCmd, progressChan, PhaseSystemPackages, 0.82, 0.83); err != nil {
|
||||||
return fmt.Errorf("failed to install rustup: %w", err)
|
return fmt.Errorf("failed to install rustup: %w", err)
|
||||||
}
|
}
|
||||||
@@ -649,7 +650,7 @@ func (u *UbuntuDistribution) installGo(ctx context.Context, sudoPassword string,
|
|||||||
CommandInfo: "sudo add-apt-repository ppa:longsleep/golang-backports",
|
CommandInfo: "sudo add-apt-repository ppa:longsleep/golang-backports",
|
||||||
}
|
}
|
||||||
|
|
||||||
addPPACmd := ExecSudoCommand(ctx, sudoPassword,
|
addPPACmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
"add-apt-repository -y ppa:longsleep/golang-backports")
|
"add-apt-repository -y ppa:longsleep/golang-backports")
|
||||||
if err := u.runWithProgress(addPPACmd, progressChan, PhaseSystemPackages, 0.87, 0.88); err != nil {
|
if err := u.runWithProgress(addPPACmd, progressChan, PhaseSystemPackages, 0.87, 0.88); err != nil {
|
||||||
return fmt.Errorf("failed to add Go PPA: %w", err)
|
return fmt.Errorf("failed to add Go PPA: %w", err)
|
||||||
@@ -664,7 +665,7 @@ func (u *UbuntuDistribution) installGo(ctx context.Context, sudoPassword string,
|
|||||||
CommandInfo: "sudo apt-get update",
|
CommandInfo: "sudo apt-get update",
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get update")
|
updateCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get update")
|
||||||
if err := u.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.88, 0.89); err != nil {
|
if err := u.runWithProgress(updateCmd, progressChan, PhaseSystemPackages, 0.88, 0.89); err != nil {
|
||||||
return fmt.Errorf("failed to update package lists after adding Go PPA: %w", err)
|
return fmt.Errorf("failed to update package lists after adding Go PPA: %w", err)
|
||||||
}
|
}
|
||||||
@@ -678,7 +679,7 @@ func (u *UbuntuDistribution) installGo(ctx context.Context, sudoPassword string,
|
|||||||
CommandInfo: "sudo apt-get install golang-go",
|
CommandInfo: "sudo apt-get install golang-go",
|
||||||
}
|
}
|
||||||
|
|
||||||
installCmd := ExecSudoCommand(ctx, sudoPassword, "apt-get install -y golang-go")
|
installCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y golang-go")
|
||||||
return u.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.89, 0.90)
|
return u.runWithProgress(installCmd, progressChan, PhaseSystemPackages, 0.89, 0.90)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/matugen"
|
||||||
sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
|
sharedpam "github.com/AvengeMedia/DankMaterialShell/core/internal/pam"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/utils"
|
||||||
"github.com/sblinch/kdl-go"
|
"github.com/sblinch/kdl-go"
|
||||||
"github.com/sblinch/kdl-go/document"
|
"github.com/sblinch/kdl-go/document"
|
||||||
@@ -327,56 +328,17 @@ func EnsureGreetdInstalled(logFunc func(string), sudoPassword string) error {
|
|||||||
|
|
||||||
switch config.Family {
|
switch config.Family {
|
||||||
case distros.FamilyArch:
|
case distros.FamilyArch:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "pacman -S --needed --noconfirm greetd")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword,
|
|
||||||
"pacman -S --needed --noconfirm greetd")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "pacman", "-S", "--needed", "--noconfirm", "greetd")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyFedora:
|
case distros.FamilyFedora:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "dnf install -y greetd")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword,
|
|
||||||
"dnf install -y greetd")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "dnf", "install", "-y", "greetd")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilySUSE:
|
case distros.FamilySUSE:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "zypper install -y greetd")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword,
|
case distros.FamilyUbuntu, distros.FamilyDebian:
|
||||||
"zypper install -y greetd")
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y greetd")
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "zypper", "install", "-y", "greetd")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyUbuntu:
|
|
||||||
if sudoPassword != "" {
|
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword,
|
|
||||||
"apt-get install -y greetd")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "greetd")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyDebian:
|
|
||||||
if sudoPassword != "" {
|
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword,
|
|
||||||
"apt-get install -y greetd")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "greetd")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyGentoo:
|
case distros.FamilyGentoo:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "emerge --ask n sys-apps/greetd")
|
||||||
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:
|
case distros.FamilyNix:
|
||||||
return fmt.Errorf("on NixOS, please add greetd to your configuration.nix")
|
return fmt.Errorf("on NixOS, please add greetd to your configuration.nix")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported distribution family for automatic greetd installation: %s", config.Family)
|
return fmt.Errorf("unsupported distribution family for automatic greetd installation: %s", config.Family)
|
||||||
}
|
}
|
||||||
@@ -437,56 +399,56 @@ func TryInstallGreeterPackage(logFunc func(string), sudoPassword string) bool {
|
|||||||
logFunc(fmt.Sprintf("Adding DankLinux OBS repository (%s)...", obsSlug))
|
logFunc(fmt.Sprintf("Adding DankLinux OBS repository (%s)...", obsSlug))
|
||||||
if _, err := exec.LookPath("gpg"); err != nil {
|
if _, err := exec.LookPath("gpg"); err != nil {
|
||||||
logFunc("Installing gnupg for OBS repository key import...")
|
logFunc("Installing gnupg for OBS repository key import...")
|
||||||
installGPGCmd := exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "gnupg")
|
installGPGCmd := privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y gnupg")
|
||||||
installGPGCmd.Stdout = os.Stdout
|
installGPGCmd.Stdout = os.Stdout
|
||||||
installGPGCmd.Stderr = os.Stderr
|
installGPGCmd.Stderr = os.Stderr
|
||||||
if err := installGPGCmd.Run(); err != nil {
|
if err := installGPGCmd.Run(); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Failed to install gnupg: %v", err))
|
logFunc(fmt.Sprintf("⚠ Failed to install gnupg: %v", err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mkdirCmd := exec.CommandContext(ctx, "sudo", "mkdir", "-p", "/etc/apt/keyrings")
|
mkdirCmd := privesc.ExecCommand(ctx, sudoPassword, "mkdir -p /etc/apt/keyrings")
|
||||||
mkdirCmd.Stdout = os.Stdout
|
mkdirCmd.Stdout = os.Stdout
|
||||||
mkdirCmd.Stderr = os.Stderr
|
mkdirCmd.Stderr = os.Stderr
|
||||||
mkdirCmd.Run()
|
mkdirCmd.Run()
|
||||||
addKeyCmd := exec.CommandContext(ctx, "bash", "-c",
|
addKeyCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf(`curl -fsSL %s | sudo gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg`, keyURL))
|
fmt.Sprintf(`bash -c "curl -fsSL %s | gpg --dearmor -o /etc/apt/keyrings/danklinux.gpg"`, keyURL))
|
||||||
addKeyCmd.Stdout = os.Stdout
|
addKeyCmd.Stdout = os.Stdout
|
||||||
addKeyCmd.Stderr = os.Stderr
|
addKeyCmd.Stderr = os.Stderr
|
||||||
addKeyCmd.Run()
|
addKeyCmd.Run()
|
||||||
addRepoCmd := exec.CommandContext(ctx, "bash", "-c",
|
addRepoCmd := privesc.ExecCommand(ctx, sudoPassword,
|
||||||
fmt.Sprintf(`echo '%s' | sudo tee /etc/apt/sources.list.d/danklinux.list`, repoLine))
|
fmt.Sprintf(`bash -c "echo '%s' > /etc/apt/sources.list.d/danklinux.list"`, repoLine))
|
||||||
addRepoCmd.Stdout = os.Stdout
|
addRepoCmd.Stdout = os.Stdout
|
||||||
addRepoCmd.Stderr = os.Stderr
|
addRepoCmd.Stderr = os.Stderr
|
||||||
addRepoCmd.Run()
|
addRepoCmd.Run()
|
||||||
exec.CommandContext(ctx, "sudo", "apt-get", "update").Run()
|
privesc.ExecCommand(ctx, sudoPassword, "apt-get update").Run()
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "dms-greeter")
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y dms-greeter")
|
||||||
case distros.FamilySUSE:
|
case distros.FamilySUSE:
|
||||||
repoURL := getOpenSUSEOBSRepoURL(osInfo)
|
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)
|
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...")
|
logFunc("Adding DankLinux OBS repository...")
|
||||||
addRepoCmd := exec.CommandContext(ctx, "sudo", "zypper", "addrepo", repoURL)
|
addRepoCmd := privesc.ExecCommand(ctx, sudoPassword, fmt.Sprintf("zypper addrepo %s", repoURL))
|
||||||
addRepoCmd.Stdout = os.Stdout
|
addRepoCmd.Stdout = os.Stdout
|
||||||
addRepoCmd.Stderr = os.Stderr
|
addRepoCmd.Stderr = os.Stderr
|
||||||
addRepoCmd.Run()
|
addRepoCmd.Run()
|
||||||
exec.CommandContext(ctx, "sudo", "zypper", "refresh").Run()
|
privesc.ExecCommand(ctx, sudoPassword, "zypper refresh").Run()
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "zypper", "install", "-y", "dms-greeter")
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "zypper install -y dms-greeter")
|
||||||
case distros.FamilyUbuntu:
|
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 -y dms-greeter"
|
failHint = "⚠ dms-greeter install failed. Add PPA manually: sudo add-apt-repository ppa:avengemedia/danklinux && sudo apt-get update && sudo apt-get install -y dms-greeter"
|
||||||
logFunc("Enabling PPA ppa:avengemedia/danklinux...")
|
logFunc("Enabling PPA ppa:avengemedia/danklinux...")
|
||||||
ppacmd := exec.CommandContext(ctx, "sudo", "add-apt-repository", "-y", "ppa:avengemedia/danklinux")
|
ppacmd := privesc.ExecCommand(ctx, sudoPassword, "add-apt-repository -y ppa:avengemedia/danklinux")
|
||||||
ppacmd.Stdout = os.Stdout
|
ppacmd.Stdout = os.Stdout
|
||||||
ppacmd.Stderr = os.Stderr
|
ppacmd.Stderr = os.Stderr
|
||||||
ppacmd.Run()
|
ppacmd.Run()
|
||||||
exec.CommandContext(ctx, "sudo", "apt-get", "update").Run()
|
privesc.ExecCommand(ctx, sudoPassword, "apt-get update").Run()
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "dms-greeter")
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y dms-greeter")
|
||||||
case distros.FamilyFedora:
|
case distros.FamilyFedora:
|
||||||
failHint = "⚠ dms-greeter install failed. Enable COPR manually: sudo dnf copr enable avengemedia/danklinux && sudo dnf install dms-greeter"
|
failHint = "⚠ dms-greeter install failed. Enable COPR manually: sudo dnf copr enable avengemedia/danklinux && sudo dnf install dms-greeter"
|
||||||
logFunc("Enabling COPR avengemedia/danklinux...")
|
logFunc("Enabling COPR avengemedia/danklinux...")
|
||||||
coprcmd := exec.CommandContext(ctx, "sudo", "dnf", "copr", "enable", "-y", "avengemedia/danklinux")
|
coprcmd := privesc.ExecCommand(ctx, sudoPassword, "dnf copr enable -y avengemedia/danklinux")
|
||||||
coprcmd.Stdout = os.Stdout
|
coprcmd.Stdout = os.Stdout
|
||||||
coprcmd.Stderr = os.Stderr
|
coprcmd.Stderr = os.Stderr
|
||||||
coprcmd.Run()
|
coprcmd.Run()
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "dnf", "install", "-y", "dms-greeter")
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "dnf install -y dms-greeter")
|
||||||
case distros.FamilyArch:
|
case distros.FamilyArch:
|
||||||
aurHelper := ""
|
aurHelper := ""
|
||||||
for _, helper := range []string{"paru", "yay"} {
|
for _, helper := range []string{"paru", "yay"} {
|
||||||
@@ -539,25 +501,25 @@ func CopyGreeterFiles(dmsPath, compositor string, logFunc func(string), sudoPass
|
|||||||
if info, err := os.Stat(wrapperDst); err == nil && !info.IsDir() {
|
if info, err := os.Stat(wrapperDst); err == nil && !info.IsDir() {
|
||||||
action = "Updated"
|
action = "Updated"
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "cp", wrapperSrc, wrapperDst); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "cp", wrapperSrc, wrapperDst); err != nil {
|
||||||
return fmt.Errorf("failed to copy dms-greeter wrapper: %w", err)
|
return fmt.Errorf("failed to copy dms-greeter wrapper: %w", err)
|
||||||
}
|
}
|
||||||
logFunc(fmt.Sprintf("✓ %s dms-greeter wrapper at %s", action, wrapperDst))
|
logFunc(fmt.Sprintf("✓ %s dms-greeter wrapper at %s", action, wrapperDst))
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "+x", wrapperDst); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "+x", wrapperDst); err != nil {
|
||||||
return fmt.Errorf("failed to make wrapper executable: %w", err)
|
return fmt.Errorf("failed to make wrapper executable: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
osInfo, err := distros.GetOSInfo()
|
osInfo, err := distros.GetOSInfo()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
if config, exists := distros.Registry[osInfo.Distribution.ID]; exists && (config.Family == distros.FamilyFedora || config.Family == distros.FamilySUSE) {
|
if config, exists := distros.Registry[osInfo.Distribution.ID]; exists && (config.Family == distros.FamilyFedora || config.Family == distros.FamilySUSE) {
|
||||||
if err := runSudoCmd(sudoPassword, "semanage", "fcontext", "-a", "-t", "bin_t", wrapperDst); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "semanage", "fcontext", "-a", "-t", "bin_t", wrapperDst); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to set SELinux fcontext: %v", err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to set SELinux fcontext: %v", err))
|
||||||
} else {
|
} else {
|
||||||
logFunc("✓ Set SELinux fcontext for dms-greeter")
|
logFunc("✓ Set SELinux fcontext for dms-greeter")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "restorecon", "-v", wrapperDst); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "restorecon", "-v", wrapperDst); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to restore SELinux context: %v", err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to restore SELinux context: %v", err))
|
||||||
} else {
|
} else {
|
||||||
logFunc("✓ Restored SELinux context for dms-greeter")
|
logFunc("✓ Restored SELinux context for dms-greeter")
|
||||||
@@ -583,7 +545,7 @@ func EnsureGreeterCacheDir(logFunc func(string), sudoPassword string) error {
|
|||||||
if !os.IsNotExist(err) {
|
if !os.IsNotExist(err) {
|
||||||
return fmt.Errorf("failed to stat cache directory: %w", err)
|
return fmt.Errorf("failed to stat cache directory: %w", err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "mkdir", "-p", cacheDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "mkdir", "-p", cacheDir); err != nil {
|
||||||
return fmt.Errorf("failed to create cache directory: %w", err)
|
return fmt.Errorf("failed to create cache directory: %w", err)
|
||||||
}
|
}
|
||||||
created = true
|
created = true
|
||||||
@@ -595,17 +557,17 @@ func EnsureGreeterCacheDir(logFunc func(string), sudoPassword string) error {
|
|||||||
daemonUser := DetectGreeterUser()
|
daemonUser := DetectGreeterUser()
|
||||||
preferredOwner := fmt.Sprintf("%s:%s", daemonUser, group)
|
preferredOwner := fmt.Sprintf("%s:%s", daemonUser, group)
|
||||||
owner := preferredOwner
|
owner := preferredOwner
|
||||||
if err := runSudoCmd(sudoPassword, "chown", owner, cacheDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chown", owner, cacheDir); err != nil {
|
||||||
// Some setups may not have a matching daemon user at this moment; fall back
|
// Some setups may not have a matching daemon user at this moment; fall back
|
||||||
// to root:<group> while still allowing group-writable greeter runtime access.
|
// to root:<group> while still allowing group-writable greeter runtime access.
|
||||||
fallbackOwner := fmt.Sprintf("root:%s", group)
|
fallbackOwner := fmt.Sprintf("root:%s", group)
|
||||||
if fallbackErr := runSudoCmd(sudoPassword, "chown", fallbackOwner, cacheDir); fallbackErr != nil {
|
if fallbackErr := privesc.Run(context.Background(), sudoPassword, "chown", fallbackOwner, cacheDir); fallbackErr != nil {
|
||||||
return fmt.Errorf("failed to set cache directory owner (preferred %s: %v; fallback %s: %w)", preferredOwner, err, fallbackOwner, fallbackErr)
|
return fmt.Errorf("failed to set cache directory owner (preferred %s: %v; fallback %s: %w)", preferredOwner, err, fallbackOwner, fallbackErr)
|
||||||
}
|
}
|
||||||
owner = fallbackOwner
|
owner = fallbackOwner
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "2770", cacheDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "2770", cacheDir); err != nil {
|
||||||
return fmt.Errorf("failed to set cache directory permissions: %w", err)
|
return fmt.Errorf("failed to set cache directory permissions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -616,13 +578,13 @@ func EnsureGreeterCacheDir(logFunc func(string), sudoPassword string) error {
|
|||||||
filepath.Join(cacheDir, ".cache"),
|
filepath.Join(cacheDir, ".cache"),
|
||||||
}
|
}
|
||||||
for _, dir := range runtimeDirs {
|
for _, dir := range runtimeDirs {
|
||||||
if err := runSudoCmd(sudoPassword, "mkdir", "-p", dir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "mkdir", "-p", dir); err != nil {
|
||||||
return fmt.Errorf("failed to create cache runtime directory %s: %w", dir, err)
|
return fmt.Errorf("failed to create cache runtime directory %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "chown", owner, dir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chown", owner, dir); err != nil {
|
||||||
return fmt.Errorf("failed to set owner for cache runtime directory %s: %w", dir, err)
|
return fmt.Errorf("failed to set owner for cache runtime directory %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "2770", dir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "2770", dir); err != nil {
|
||||||
return fmt.Errorf("failed to set permissions for cache runtime directory %s: %w", dir, err)
|
return fmt.Errorf("failed to set permissions for cache runtime directory %s: %w", dir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,7 +596,7 @@ func EnsureGreeterCacheDir(logFunc func(string), sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if isSELinuxEnforcing() && utils.CommandExists("restorecon") {
|
if isSELinuxEnforcing() && utils.CommandExists("restorecon") {
|
||||||
if err := runSudoCmd(sudoPassword, "restorecon", "-Rv", cacheDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "restorecon", "-Rv", cacheDir); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to restore SELinux context for %s: %v", cacheDir, err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to restore SELinux context for %s: %v", cacheDir, err))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -659,13 +621,13 @@ func ensureGreeterMemoryCompatLink(logFunc func(string), sudoPassword, legacyPat
|
|||||||
info, err := os.Lstat(legacyPath)
|
info, err := os.Lstat(legacyPath)
|
||||||
if err == nil && info.Mode().IsRegular() {
|
if err == nil && info.Mode().IsRegular() {
|
||||||
if _, stateErr := os.Stat(statePath); os.IsNotExist(stateErr) {
|
if _, stateErr := os.Stat(statePath); os.IsNotExist(stateErr) {
|
||||||
if copyErr := runSudoCmd(sudoPassword, "cp", "-f", legacyPath, statePath); copyErr != nil {
|
if copyErr := privesc.Run(context.Background(), sudoPassword, "cp", "-f", legacyPath, statePath); copyErr != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to migrate legacy greeter memory file to %s: %v", statePath, copyErr))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to migrate legacy greeter memory file to %s: %v", statePath, copyErr))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "ln", "-sfn", statePath, legacyPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "ln", "-sfn", statePath, legacyPath); err != nil {
|
||||||
return fmt.Errorf("failed to create greeter memory compatibility symlink %s -> %s: %w", legacyPath, statePath, err)
|
return fmt.Errorf("failed to create greeter memory compatibility symlink %s -> %s: %w", legacyPath, statePath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -692,7 +654,7 @@ func InstallAppArmorProfile(logFunc func(string), sudoPassword string) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "mkdir", "-p", "/etc/apparmor.d"); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "mkdir", "-p", "/etc/apparmor.d"); err != nil {
|
||||||
return fmt.Errorf("failed to create /etc/apparmor.d: %w", err)
|
return fmt.Errorf("failed to create /etc/apparmor.d: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -709,15 +671,15 @@ func InstallAppArmorProfile(logFunc func(string), sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
tmp.Close()
|
tmp.Close()
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "cp", tmpPath, appArmorProfileDest); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "cp", tmpPath, appArmorProfileDest); err != nil {
|
||||||
return fmt.Errorf("failed to install AppArmor profile to %s: %w", appArmorProfileDest, err)
|
return fmt.Errorf("failed to install AppArmor profile to %s: %w", appArmorProfileDest, err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "644", appArmorProfileDest); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "644", appArmorProfileDest); err != nil {
|
||||||
return fmt.Errorf("failed to set AppArmor profile permissions: %w", err)
|
return fmt.Errorf("failed to set AppArmor profile permissions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if utils.CommandExists("apparmor_parser") {
|
if utils.CommandExists("apparmor_parser") {
|
||||||
if err := runSudoCmd(sudoPassword, "apparmor_parser", "-r", appArmorProfileDest); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "apparmor_parser", "-r", appArmorProfileDest); err != nil {
|
||||||
logFunc(fmt.Sprintf(" ⚠ AppArmor profile installed but reload failed: %v", err))
|
logFunc(fmt.Sprintf(" ⚠ AppArmor profile installed but reload failed: %v", err))
|
||||||
logFunc(" Run: sudo apparmor_parser -r " + appArmorProfileDest)
|
logFunc(" Run: sudo apparmor_parser -r " + appArmorProfileDest)
|
||||||
} else {
|
} else {
|
||||||
@@ -745,9 +707,9 @@ func UninstallAppArmorProfile(logFunc func(string), sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if utils.CommandExists("apparmor_parser") {
|
if utils.CommandExists("apparmor_parser") {
|
||||||
_ = runSudoCmd(sudoPassword, "apparmor_parser", "--remove", appArmorProfileDest)
|
_ = privesc.Run(context.Background(), sudoPassword, "apparmor_parser", "--remove", appArmorProfileDest)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "rm", "-f", appArmorProfileDest); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "rm", "-f", appArmorProfileDest); err != nil {
|
||||||
return fmt.Errorf("failed to remove AppArmor profile: %w", err)
|
return fmt.Errorf("failed to remove AppArmor profile: %w", err)
|
||||||
}
|
}
|
||||||
logFunc(" ✓ Removed DMS AppArmor profile")
|
logFunc(" ✓ Removed DMS AppArmor profile")
|
||||||
@@ -777,50 +739,17 @@ func EnsureACLInstalled(logFunc func(string), sudoPassword string) error {
|
|||||||
|
|
||||||
switch config.Family {
|
switch config.Family {
|
||||||
case distros.FamilyArch:
|
case distros.FamilyArch:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "pacman -S --needed --noconfirm acl")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "pacman -S --needed --noconfirm acl")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "pacman", "-S", "--needed", "--noconfirm", "acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyFedora:
|
case distros.FamilyFedora:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "dnf install -y acl")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "dnf install -y acl")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "dnf", "install", "-y", "acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilySUSE:
|
case distros.FamilySUSE:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "zypper install -y acl")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "zypper install -y acl")
|
case distros.FamilyUbuntu, distros.FamilyDebian:
|
||||||
} else {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "apt-get install -y acl")
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "zypper", "install", "-y", "acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyUbuntu:
|
|
||||||
if sudoPassword != "" {
|
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "apt-get install -y acl")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyDebian:
|
|
||||||
if sudoPassword != "" {
|
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "apt-get install -y acl")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "apt-get", "install", "-y", "acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyGentoo:
|
case distros.FamilyGentoo:
|
||||||
if sudoPassword != "" {
|
installCmd = privesc.ExecCommand(ctx, sudoPassword, "emerge --ask n sys-fs/acl")
|
||||||
installCmd = distros.ExecSudoCommand(ctx, sudoPassword, "emerge --ask n sys-fs/acl")
|
|
||||||
} else {
|
|
||||||
installCmd = exec.CommandContext(ctx, "sudo", "emerge", "--ask", "n", "sys-fs/acl")
|
|
||||||
}
|
|
||||||
|
|
||||||
case distros.FamilyNix:
|
case distros.FamilyNix:
|
||||||
return fmt.Errorf("on NixOS, please add pkgs.acl to your configuration.nix")
|
return fmt.Errorf("on NixOS, please add pkgs.acl to your configuration.nix")
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unsupported distribution family for automatic acl installation: %s", config.Family)
|
return fmt.Errorf("unsupported distribution family for automatic acl installation: %s", config.Family)
|
||||||
}
|
}
|
||||||
@@ -877,7 +806,7 @@ func SetupParentDirectoryACLs(logFunc func(string), sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Group ACL covers daemon users regardless of username (e.g. greetd ≠ greeter on Fedora).
|
// Group ACL covers daemon users regardless of username (e.g. greetd ≠ greeter on Fedora).
|
||||||
if err := runSudoCmd(sudoPassword, "setfacl", "-m", fmt.Sprintf("g:%s:rX", group), dir.path); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "setfacl", "-m", fmt.Sprintf("g:%s:rX", group), dir.path); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to set ACL on %s: %v", dir.desc, err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to set ACL on %s: %v", dir.desc, err))
|
||||||
logFunc(fmt.Sprintf(" You may need to run manually: setfacl -m g:%s:rX %s", group, dir.path))
|
logFunc(fmt.Sprintf(" You may need to run manually: setfacl -m g:%s:rX %s", group, dir.path))
|
||||||
continue
|
continue
|
||||||
@@ -934,7 +863,7 @@ func RemediateStaleACLs(logFunc func(string), sudoPassword string) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
for _, user := range existingUsers {
|
for _, user := range existingUsers {
|
||||||
_ = runSudoCmd(sudoPassword, "setfacl", "-x", fmt.Sprintf("u:%s", user), dir)
|
_ = privesc.Run(context.Background(), sudoPassword, "setfacl", "-x", fmt.Sprintf("u:%s", user), dir)
|
||||||
cleaned = true
|
cleaned = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -974,7 +903,7 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
|||||||
|
|
||||||
// Create the group if it doesn't exist yet (e.g. before greetd package is installed).
|
// Create the group if it doesn't exist yet (e.g. before greetd package is installed).
|
||||||
if !utils.HasGroup(group) {
|
if !utils.HasGroup(group) {
|
||||||
if err := runSudoCmd(sudoPassword, "groupadd", "-r", group); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "groupadd", "-r", group); err != nil {
|
||||||
return fmt.Errorf("failed to create %s group: %w", group, err)
|
return fmt.Errorf("failed to create %s group: %w", group, err)
|
||||||
}
|
}
|
||||||
logFunc(fmt.Sprintf("✓ Created system group %s", group))
|
logFunc(fmt.Sprintf("✓ Created system group %s", group))
|
||||||
@@ -985,7 +914,7 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
|||||||
if err == nil && strings.Contains(string(groupsOutput), group) {
|
if err == nil && strings.Contains(string(groupsOutput), group) {
|
||||||
logFunc(fmt.Sprintf("✓ %s is already in %s group", currentUser, group))
|
logFunc(fmt.Sprintf("✓ %s is already in %s group", currentUser, group))
|
||||||
} else {
|
} else {
|
||||||
if err := runSudoCmd(sudoPassword, "usermod", "-aG", group, currentUser); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "usermod", "-aG", group, currentUser); err != nil {
|
||||||
return fmt.Errorf("failed to add %s to %s group: %w", currentUser, group, err)
|
return fmt.Errorf("failed to add %s to %s group: %w", currentUser, group, err)
|
||||||
}
|
}
|
||||||
logFunc(fmt.Sprintf("✓ Added %s to %s group (logout/login required for changes to take effect)", currentUser, group))
|
logFunc(fmt.Sprintf("✓ Added %s to %s group (logout/login required for changes to take effect)", currentUser, group))
|
||||||
@@ -1000,7 +929,7 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
|||||||
if strings.Contains(string(daemonGroupsOutput), group) {
|
if strings.Contains(string(daemonGroupsOutput), group) {
|
||||||
logFunc(fmt.Sprintf("✓ Greeter daemon user %s is already in %s group", daemonUser, group))
|
logFunc(fmt.Sprintf("✓ Greeter daemon user %s is already in %s group", daemonUser, group))
|
||||||
} else {
|
} else {
|
||||||
if err := runSudoCmd(sudoPassword, "usermod", "-aG", group, daemonUser); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "usermod", "-aG", group, daemonUser); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: could not add %s to %s group: %v", daemonUser, group, err))
|
logFunc(fmt.Sprintf("⚠ Warning: could not add %s to %s group: %v", daemonUser, group, err))
|
||||||
} else {
|
} else {
|
||||||
logFunc(fmt.Sprintf("✓ Added greeter daemon user %s to %s group", daemonUser, group))
|
logFunc(fmt.Sprintf("✓ Added greeter daemon user %s to %s group", daemonUser, group))
|
||||||
@@ -1030,12 +959,12 @@ func SetupDMSGroup(logFunc func(string), sudoPassword string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "chgrp", "-R", group, dir.path); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chgrp", "-R", group, dir.path); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to set group for %s: %v", dir.desc, err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to set group for %s: %v", dir.desc, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "-R", "g+rX", dir.path); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "-R", "g+rX", dir.path); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to set permissions for %s: %v", dir.desc, err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to set permissions for %s: %v", dir.desc, err))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -1247,8 +1176,8 @@ func syncGreeterColorSource(homeDir, cacheDir string, state greeterThemeSyncStat
|
|||||||
}
|
}
|
||||||
|
|
||||||
target := filepath.Join(cacheDir, "colors.json")
|
target := filepath.Join(cacheDir, "colors.json")
|
||||||
_ = runSudoCmd(sudoPassword, "rm", "-f", target)
|
_ = privesc.Run(context.Background(), sudoPassword, "rm", "-f", target)
|
||||||
if err := runSudoCmd(sudoPassword, "ln", "-sf", source, target); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "ln", "-sf", source, target); err != nil {
|
||||||
return fmt.Errorf("failed to create symlink for wallpaper based theming (%s -> %s): %w", target, source, err)
|
return fmt.Errorf("failed to create symlink for wallpaper based theming (%s -> %s): %w", target, source, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1300,9 +1229,9 @@ func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPasswo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = runSudoCmd(sudoPassword, "rm", "-f", link.target)
|
_ = privesc.Run(context.Background(), sudoPassword, "rm", "-f", link.target)
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "ln", "-sf", link.source, link.target); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "ln", "-sf", link.source, link.target); err != nil {
|
||||||
return fmt.Errorf("failed to create symlink for %s (%s -> %s): %w", link.desc, link.target, link.source, err)
|
return fmt.Errorf("failed to create symlink for %s (%s -> %s): %w", link.desc, link.target, link.source, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1340,13 +1269,13 @@ func SyncDMSConfigs(dmsPath, compositor string, logFunc func(string), sudoPasswo
|
|||||||
func syncGreeterWallpaperOverride(cacheDir string, logFunc func(string), sudoPassword string, state greeterThemeSyncState) error {
|
func syncGreeterWallpaperOverride(cacheDir string, logFunc func(string), sudoPassword string, state greeterThemeSyncState) error {
|
||||||
destPath := filepath.Join(cacheDir, "greeter_wallpaper_override.jpg")
|
destPath := filepath.Join(cacheDir, "greeter_wallpaper_override.jpg")
|
||||||
if state.ResolvedGreeterWallpaperPath == "" {
|
if state.ResolvedGreeterWallpaperPath == "" {
|
||||||
if err := runSudoCmd(sudoPassword, "rm", "-f", destPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "rm", "-f", destPath); err != nil {
|
||||||
return fmt.Errorf("failed to clear override file %s: %w", destPath, err)
|
return fmt.Errorf("failed to clear override file %s: %w", destPath, err)
|
||||||
}
|
}
|
||||||
logFunc("✓ Cleared greeter wallpaper override")
|
logFunc("✓ Cleared greeter wallpaper override")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "rm", "-f", destPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "rm", "-f", destPath); err != nil {
|
||||||
return fmt.Errorf("failed to remove old override file %s: %w", destPath, err)
|
return fmt.Errorf("failed to remove old override file %s: %w", destPath, err)
|
||||||
}
|
}
|
||||||
src := state.ResolvedGreeterWallpaperPath
|
src := state.ResolvedGreeterWallpaperPath
|
||||||
@@ -1357,17 +1286,17 @@ func syncGreeterWallpaperOverride(cacheDir string, logFunc func(string), sudoPas
|
|||||||
if st.IsDir() {
|
if st.IsDir() {
|
||||||
return fmt.Errorf("configured greeter wallpaper path points to a directory: %s", src)
|
return fmt.Errorf("configured greeter wallpaper path points to a directory: %s", src)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "cp", src, destPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "cp", src, destPath); err != nil {
|
||||||
return fmt.Errorf("failed to copy override wallpaper to %s: %w", destPath, err)
|
return fmt.Errorf("failed to copy override wallpaper to %s: %w", destPath, err)
|
||||||
}
|
}
|
||||||
greeterGroup := DetectGreeterGroup()
|
greeterGroup := DetectGreeterGroup()
|
||||||
daemonUser := DetectGreeterUser()
|
daemonUser := DetectGreeterUser()
|
||||||
if err := runSudoCmd(sudoPassword, "chown", daemonUser+":"+greeterGroup, destPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chown", daemonUser+":"+greeterGroup, destPath); err != nil {
|
||||||
if fallbackErr := runSudoCmd(sudoPassword, "chown", "root:"+greeterGroup, destPath); fallbackErr != nil {
|
if fallbackErr := privesc.Run(context.Background(), sudoPassword, "chown", "root:"+greeterGroup, destPath); fallbackErr != nil {
|
||||||
return fmt.Errorf("failed to set override ownership on %s: %w", destPath, err)
|
return fmt.Errorf("failed to set override ownership on %s: %w", destPath, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "644", destPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "644", destPath); err != nil {
|
||||||
return fmt.Errorf("failed to set override permissions on %s: %w", destPath, err)
|
return fmt.Errorf("failed to set override permissions on %s: %w", destPath, err)
|
||||||
}
|
}
|
||||||
logFunc("✓ Synced greeter wallpaper override")
|
logFunc("✓ Synced greeter wallpaper override")
|
||||||
@@ -1422,13 +1351,13 @@ func syncNiriGreeterConfig(logFunc func(string), sudoPassword string) error {
|
|||||||
|
|
||||||
greeterDir := "/etc/greetd/niri"
|
greeterDir := "/etc/greetd/niri"
|
||||||
greeterGroup := DetectGreeterGroup()
|
greeterGroup := DetectGreeterGroup()
|
||||||
if err := runSudoCmd(sudoPassword, "mkdir", "-p", greeterDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "mkdir", "-p", greeterDir); err != nil {
|
||||||
return fmt.Errorf("failed to create greetd niri directory: %w", err)
|
return fmt.Errorf("failed to create greetd niri directory: %w", err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "chown", fmt.Sprintf("root:%s", greeterGroup), greeterDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chown", fmt.Sprintf("root:%s", greeterGroup), greeterDir); err != nil {
|
||||||
return fmt.Errorf("failed to set greetd niri directory ownership: %w", err)
|
return fmt.Errorf("failed to set greetd niri directory ownership: %w", err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "chmod", "755", greeterDir); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "chmod", "755", greeterDir); err != nil {
|
||||||
return fmt.Errorf("failed to set greetd niri directory permissions: %w", err)
|
return fmt.Errorf("failed to set greetd niri directory permissions: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1450,7 +1379,7 @@ func syncNiriGreeterConfig(logFunc func(string), sudoPassword string) error {
|
|||||||
if err := backupFileIfExists(sudoPassword, dmsPath, ".backup"); err != nil {
|
if err := backupFileIfExists(sudoPassword, dmsPath, ".backup"); err != nil {
|
||||||
return fmt.Errorf("failed to backup %s: %w", dmsPath, err)
|
return fmt.Errorf("failed to backup %s: %w", dmsPath, err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "install", "-o", "root", "-g", greeterGroup, "-m", "0644", dmsTemp.Name(), dmsPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "install", "-o", "root", "-g", greeterGroup, "-m", "0644", dmsTemp.Name(), dmsPath); err != nil {
|
||||||
return fmt.Errorf("failed to install greetd niri dms config: %w", err)
|
return fmt.Errorf("failed to install greetd niri dms config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1473,7 +1402,7 @@ func syncNiriGreeterConfig(logFunc func(string), sudoPassword string) error {
|
|||||||
if err := backupFileIfExists(sudoPassword, mainPath, ".backup"); err != nil {
|
if err := backupFileIfExists(sudoPassword, mainPath, ".backup"); err != nil {
|
||||||
return fmt.Errorf("failed to backup %s: %w", mainPath, err)
|
return fmt.Errorf("failed to backup %s: %w", mainPath, err)
|
||||||
}
|
}
|
||||||
if err := runSudoCmd(sudoPassword, "install", "-o", "root", "-g", greeterGroup, "-m", "0644", mainTemp.Name(), mainPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "install", "-o", "root", "-g", greeterGroup, "-m", "0644", mainTemp.Name(), mainPath); err != nil {
|
||||||
return fmt.Errorf("failed to install greetd niri main config: %w", err)
|
return fmt.Errorf("failed to install greetd niri main config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1549,7 +1478,7 @@ func ensureGreetdNiriConfig(logFunc func(string), sudoPassword string, niriConfi
|
|||||||
return fmt.Errorf("failed to close temp greetd config: %w", err)
|
return fmt.Errorf("failed to close temp greetd config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "mv", tmpFile.Name(), configPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "mv", tmpFile.Name(), configPath); err != nil {
|
||||||
return fmt.Errorf("failed to update greetd config: %w", err)
|
return fmt.Errorf("failed to update greetd config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1565,10 +1494,10 @@ func backupFileIfExists(sudoPassword string, path string, suffix string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
backupPath := fmt.Sprintf("%s%s-%s", path, suffix, time.Now().Format("20060102-150405"))
|
backupPath := fmt.Sprintf("%s%s-%s", path, suffix, time.Now().Format("20060102-150405"))
|
||||||
if err := runSudoCmd(sudoPassword, "cp", path, backupPath); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "cp", path, backupPath); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return runSudoCmd(sudoPassword, "chmod", "644", backupPath)
|
return privesc.Run(context.Background(), sudoPassword, "chmod", "644", backupPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *niriGreeterSync) processFile(filePath string) error {
|
func (s *niriGreeterSync) processFile(filePath string) error {
|
||||||
@@ -1804,11 +1733,11 @@ vt = 1
|
|||||||
return fmt.Errorf("failed to close temp greetd config: %w", err)
|
return fmt.Errorf("failed to close temp greetd config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := runSudoCmd(sudoPassword, "mkdir", "-p", "/etc/greetd"); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "mkdir", "-p", "/etc/greetd"); err != nil {
|
||||||
return fmt.Errorf("failed to create /etc/greetd: %w", err)
|
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 {
|
if err := privesc.Run(context.Background(), sudoPassword, "install", "-o", "root", "-g", "root", "-m", "0644", tmpFile.Name(), configPath); err != nil {
|
||||||
return fmt.Errorf("failed to install greetd config: %w", err)
|
return fmt.Errorf("failed to install greetd config: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1912,27 +1841,6 @@ func getOpenSUSEOBSRepoURL(osInfo *distros.OSInfo) string {
|
|||||||
return fmt.Sprintf("%s/%s/home:AvengeMedia:danklinux.repo", base, slug)
|
return fmt.Sprintf("%s/%s/home:AvengeMedia:danklinux.repo", base, slug)
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSudoCmd(sudoPassword string, command string, args ...string) error {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
|
|
||||||
if sudoPassword != "" {
|
|
||||||
fullArgs := append([]string{command}, args...)
|
|
||||||
quotedArgs := make([]string, len(fullArgs))
|
|
||||||
for i, arg := range fullArgs {
|
|
||||||
quotedArgs[i] = "'" + strings.ReplaceAll(arg, "'", "'\\''") + "'"
|
|
||||||
}
|
|
||||||
cmdStr := strings.Join(quotedArgs, " ")
|
|
||||||
|
|
||||||
cmd = distros.ExecSudoCommand(context.Background(), sudoPassword, cmdStr)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("sudo", append([]string{command}, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|
||||||
func checkSystemdEnabled(service string) (string, error) {
|
func checkSystemdEnabled(service string) (string, error) {
|
||||||
cmd := exec.Command("systemctl", "is-enabled", service)
|
cmd := exec.Command("systemctl", "is-enabled", service)
|
||||||
output, _ := cmd.Output()
|
output, _ := cmd.Output()
|
||||||
@@ -1949,7 +1857,7 @@ func DisableConflictingDisplayManagers(sudoPassword string, logFunc func(string)
|
|||||||
switch state {
|
switch state {
|
||||||
case "enabled", "enabled-runtime", "static", "indirect", "alias":
|
case "enabled", "enabled-runtime", "static", "indirect", "alias":
|
||||||
logFunc(fmt.Sprintf("Disabling conflicting display manager: %s", dm))
|
logFunc(fmt.Sprintf("Disabling conflicting display manager: %s", dm))
|
||||||
if err := runSudoCmd(sudoPassword, "systemctl", "disable", dm); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "systemctl", "disable", dm); err != nil {
|
||||||
logFunc(fmt.Sprintf("⚠ Warning: Failed to disable %s: %v", dm, err))
|
logFunc(fmt.Sprintf("⚠ Warning: Failed to disable %s: %v", dm, err))
|
||||||
} else {
|
} else {
|
||||||
logFunc(fmt.Sprintf("✓ Disabled %s", dm))
|
logFunc(fmt.Sprintf("✓ Disabled %s", dm))
|
||||||
@@ -1970,13 +1878,13 @@ func EnableGreetd(sudoPassword string, logFunc func(string)) error {
|
|||||||
}
|
}
|
||||||
if state == "masked" || state == "masked-runtime" {
|
if state == "masked" || state == "masked-runtime" {
|
||||||
logFunc(" Unmasking greetd...")
|
logFunc(" Unmasking greetd...")
|
||||||
if err := runSudoCmd(sudoPassword, "systemctl", "unmask", "greetd"); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "systemctl", "unmask", "greetd"); err != nil {
|
||||||
return fmt.Errorf("failed to unmask greetd: %w", err)
|
return fmt.Errorf("failed to unmask greetd: %w", err)
|
||||||
}
|
}
|
||||||
logFunc(" ✓ Unmasked greetd")
|
logFunc(" ✓ Unmasked greetd")
|
||||||
}
|
}
|
||||||
logFunc(" Enabling greetd service (--force)...")
|
logFunc(" Enabling greetd service (--force)...")
|
||||||
if err := runSudoCmd(sudoPassword, "systemctl", "enable", "--force", "greetd"); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "systemctl", "enable", "--force", "greetd"); err != nil {
|
||||||
return fmt.Errorf("failed to enable greetd: %w", err)
|
return fmt.Errorf("failed to enable greetd: %w", err)
|
||||||
}
|
}
|
||||||
logFunc("✓ greetd enabled")
|
logFunc("✓ greetd enabled")
|
||||||
@@ -1996,7 +1904,7 @@ func EnsureGraphicalTarget(sudoPassword string, logFunc func(string)) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
logFunc(fmt.Sprintf(" Setting default target to graphical.target (was: %s)...", current))
|
logFunc(fmt.Sprintf(" Setting default target to graphical.target (was: %s)...", current))
|
||||||
if err := runSudoCmd(sudoPassword, "systemctl", "set-default", "graphical.target"); err != nil {
|
if err := privesc.Run(context.Background(), sudoPassword, "systemctl", "set-default", "graphical.target"); err != nil {
|
||||||
return fmt.Errorf("failed to set graphical target: %w", err)
|
return fmt.Errorf("failed to set graphical target: %w", err)
|
||||||
}
|
}
|
||||||
logFunc("✓ Default target set to graphical.target")
|
logFunc("✓ Default target set to graphical.target")
|
||||||
|
|||||||
@@ -4,13 +4,13 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/config"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/greeter"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ErrConfirmationRequired is returned when --yes is not set and the user
|
// ErrConfirmationRequired is returned when --yes is not set and the user
|
||||||
@@ -383,20 +383,41 @@ func (r *Runner) parseTerminal() (deps.Terminal, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) resolveSudoPassword() (string, error) {
|
func (r *Runner) resolveSudoPassword() (string, error) {
|
||||||
// Check if sudo credentials are cached (via sudo -v or NOPASSWD)
|
tool, err := privesc.Detect()
|
||||||
cmd := exec.Command("sudo", "-n", "true")
|
if err != nil {
|
||||||
if err := cmd.Run(); err == nil {
|
return "", err
|
||||||
r.log("sudo cache is valid, no password needed")
|
}
|
||||||
fmt.Fprintln(os.Stdout, "sudo: using cached credentials")
|
|
||||||
|
if err := privesc.CheckCached(context.Background()); err == nil {
|
||||||
|
r.log(fmt.Sprintf("%s cache is valid, no password needed", tool.Name()))
|
||||||
|
fmt.Fprintf(os.Stdout, "%s: using cached credentials\n", tool.Name())
|
||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return "", fmt.Errorf(
|
switch tool {
|
||||||
"sudo authentication required but no cached credentials found\n" +
|
case privesc.ToolSudo:
|
||||||
"Options:\n" +
|
return "", fmt.Errorf(
|
||||||
" 1. Run 'sudo -v' before dankinstall to cache credentials\n" +
|
"sudo authentication required but no cached credentials found\n" +
|
||||||
" 2. Configure passwordless sudo for your user",
|
"Options:\n" +
|
||||||
)
|
" 1. Run 'sudo -v' before dankinstall to cache credentials\n" +
|
||||||
|
" 2. Configure passwordless sudo for your user",
|
||||||
|
)
|
||||||
|
case privesc.ToolDoas:
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"doas authentication required but no cached credentials found\n" +
|
||||||
|
"Options:\n" +
|
||||||
|
" 1. Run 'doas true' before dankinstall to cache credentials (requires 'persist' in /etc/doas.conf)\n" +
|
||||||
|
" 2. Configure a 'nopass' rule in /etc/doas.conf for your user",
|
||||||
|
)
|
||||||
|
case privesc.ToolRun0:
|
||||||
|
return "", fmt.Errorf(
|
||||||
|
"run0 authentication required but no cached credentials found\n" +
|
||||||
|
"Configure a polkit rule granting your user passwordless privilege\n" +
|
||||||
|
"(see `man polkit` for details on rules in /etc/polkit-1/rules.d/)",
|
||||||
|
)
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("unsupported privilege tool: %s", tool)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *Runner) anyConfigEnabled(m map[string]bool) bool {
|
func (r *Runner) anyConfigEnabled(m map[string]bool) bool {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -80,16 +81,18 @@ type lockscreenPamResolver struct {
|
|||||||
|
|
||||||
func defaultSyncDeps() syncDeps {
|
func defaultSyncDeps() syncDeps {
|
||||||
return syncDeps{
|
return syncDeps{
|
||||||
pamDir: "/etc/pam.d",
|
pamDir: "/etc/pam.d",
|
||||||
greetdPath: GreetdPamPath,
|
greetdPath: GreetdPamPath,
|
||||||
dankshellPath: DankshellPamPath,
|
dankshellPath: DankshellPamPath,
|
||||||
dankshellU2fPath: DankshellU2FPamPath,
|
dankshellU2fPath: DankshellU2FPamPath,
|
||||||
isNixOS: IsNixOS,
|
isNixOS: IsNixOS,
|
||||||
readFile: os.ReadFile,
|
readFile: os.ReadFile,
|
||||||
stat: os.Stat,
|
stat: os.Stat,
|
||||||
createTemp: os.CreateTemp,
|
createTemp: os.CreateTemp,
|
||||||
removeFile: os.Remove,
|
removeFile: os.Remove,
|
||||||
runSudoCmd: runSudoCmd,
|
runSudoCmd: func(password, command string, args ...string) error {
|
||||||
|
return privesc.Run(context.Background(), password, append([]string{command}, args...)...)
|
||||||
|
},
|
||||||
pamModuleExists: pamModuleExists,
|
pamModuleExists: pamModuleExists,
|
||||||
fingerprintAvailableForCurrentUser: FingerprintAuthAvailableForCurrentUser,
|
fingerprintAvailableForCurrentUser: FingerprintAuthAvailableForCurrentUser,
|
||||||
}
|
}
|
||||||
@@ -869,24 +872,3 @@ func fingerprintAuthAvailableForUser(username string) bool {
|
|||||||
}
|
}
|
||||||
return hasEnrolledFingerprintOutput(string(out))
|
return hasEnrolledFingerprintOutput(string(out))
|
||||||
}
|
}
|
||||||
|
|
||||||
func runSudoCmd(sudoPassword string, command string, args ...string) error {
|
|
||||||
var cmd *exec.Cmd
|
|
||||||
|
|
||||||
if sudoPassword != "" {
|
|
||||||
fullArgs := append([]string{command}, args...)
|
|
||||||
quotedArgs := make([]string, len(fullArgs))
|
|
||||||
for i, arg := range fullArgs {
|
|
||||||
quotedArgs[i] = "'" + strings.ReplaceAll(arg, "'", "'\\''") + "'"
|
|
||||||
}
|
|
||||||
cmdStr := strings.Join(quotedArgs, " ")
|
|
||||||
|
|
||||||
cmd = distros.ExecSudoCommand(context.Background(), sudoPassword, cmdStr)
|
|
||||||
} else {
|
|
||||||
cmd = exec.Command("sudo", append([]string{command}, args...)...)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmd.Stdout = os.Stdout
|
|
||||||
cmd.Stderr = os.Stderr
|
|
||||||
return cmd.Run()
|
|
||||||
}
|
|
||||||
|
|||||||
385
core/internal/privesc/privesc.go
Normal file
385
core/internal/privesc/privesc.go
Normal file
@@ -0,0 +1,385 @@
|
|||||||
|
package privesc
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Tool identifies a privilege-escalation binary.
|
||||||
|
type Tool string
|
||||||
|
|
||||||
|
const (
|
||||||
|
ToolSudo Tool = "sudo"
|
||||||
|
ToolDoas Tool = "doas"
|
||||||
|
ToolRun0 Tool = "run0"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvVar selects a specific tool when set to one of: sudo, doas, run0.
|
||||||
|
const EnvVar = "DMS_PRIVESC"
|
||||||
|
|
||||||
|
var detectionOrder = []Tool{ToolSudo, ToolDoas, ToolRun0}
|
||||||
|
|
||||||
|
var (
|
||||||
|
detectOnce sync.Once
|
||||||
|
detected Tool
|
||||||
|
detectErr error
|
||||||
|
userSelected bool
|
||||||
|
)
|
||||||
|
|
||||||
|
// Detect returns the tool that should be used for privilege escalation.
|
||||||
|
// The result is cached after the first call.
|
||||||
|
func Detect() (Tool, error) {
|
||||||
|
detectOnce.Do(func() {
|
||||||
|
detected, detectErr = detectTool()
|
||||||
|
})
|
||||||
|
return detected, detectErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// ResetForTesting clears cached detection state.
|
||||||
|
func ResetForTesting() {
|
||||||
|
detectOnce = sync.Once{}
|
||||||
|
detected = ""
|
||||||
|
detectErr = nil
|
||||||
|
userSelected = false
|
||||||
|
}
|
||||||
|
|
||||||
|
// AvailableTools returns the set of supported tools that are installed on
|
||||||
|
// PATH, in detection-precedence order.
|
||||||
|
func AvailableTools() []Tool {
|
||||||
|
var out []Tool
|
||||||
|
for _, t := range detectionOrder {
|
||||||
|
if t.Available() {
|
||||||
|
out = append(out, t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// EnvOverride returns the tool selected by the $DMS_PRIVESC env var (if any)
|
||||||
|
// along with ok=true when the variable is set. An empty or unset variable
|
||||||
|
// returns ok=false.
|
||||||
|
func EnvOverride() (Tool, bool) {
|
||||||
|
v := strings.ToLower(strings.TrimSpace(os.Getenv(EnvVar)))
|
||||||
|
if v == "" {
|
||||||
|
return "", false
|
||||||
|
}
|
||||||
|
return Tool(v), true
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetTool forces the detected tool to t, bypassing autodetection. Intended
|
||||||
|
// for use after the caller has prompted the user for a selection.
|
||||||
|
func SetTool(t Tool) error {
|
||||||
|
if !t.Available() {
|
||||||
|
return fmt.Errorf("%q is not installed", t.Name())
|
||||||
|
}
|
||||||
|
detectOnce = sync.Once{}
|
||||||
|
detectOnce.Do(func() {
|
||||||
|
detected = t
|
||||||
|
detectErr = nil
|
||||||
|
})
|
||||||
|
userSelected = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func detectTool() (Tool, error) {
|
||||||
|
switch override := strings.ToLower(strings.TrimSpace(os.Getenv(EnvVar))); override {
|
||||||
|
case "":
|
||||||
|
// fall through to autodetect
|
||||||
|
case string(ToolSudo), string(ToolDoas), string(ToolRun0):
|
||||||
|
t := Tool(override)
|
||||||
|
if !t.Available() {
|
||||||
|
return "", fmt.Errorf("%s=%s but %q is not installed", EnvVar, override, t.Name())
|
||||||
|
}
|
||||||
|
return t, nil
|
||||||
|
default:
|
||||||
|
return "", fmt.Errorf("invalid %s=%q: must be one of sudo, doas, run0", EnvVar, override)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, t := range detectionOrder {
|
||||||
|
if t.Available() {
|
||||||
|
return t, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "", fmt.Errorf("no supported privilege escalation tool found (tried: sudo, doas, run0)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Name returns the binary name.
|
||||||
|
func (t Tool) Name() string { return string(t) }
|
||||||
|
|
||||||
|
// Available reports whether this tool's binary is on PATH.
|
||||||
|
func (t Tool) Available() bool {
|
||||||
|
if t == "" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
_, err := exec.LookPath(string(t))
|
||||||
|
return err == nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SupportsStdinPassword reports whether the tool can accept a password via
|
||||||
|
// stdin. Only sudo (-S) supports this.
|
||||||
|
func (t Tool) SupportsStdinPassword() bool {
|
||||||
|
return t == ToolSudo
|
||||||
|
}
|
||||||
|
|
||||||
|
// EscapeSingleQuotes escapes single quotes for safe inclusion inside a
|
||||||
|
// bash single-quoted string.
|
||||||
|
func EscapeSingleQuotes(s string) string {
|
||||||
|
return strings.ReplaceAll(s, "'", "'\\''")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MakeCommand returns a bash command string that runs `command` with the
|
||||||
|
// detected tool. When the tool supports stdin passwords and password is
|
||||||
|
// non-empty, the password is piped in. Otherwise the tool is invoked with
|
||||||
|
// no non-interactive flag so that an interactive TTY prompt is still
|
||||||
|
// possible for CLI callers.
|
||||||
|
//
|
||||||
|
// If detection fails, the returned shell string exits 1 with an error
|
||||||
|
// message so callers that treat the *exec.Cmd as infallible still fail
|
||||||
|
// deterministically.
|
||||||
|
func MakeCommand(password, command string) string {
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return failingShell(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case ToolSudo:
|
||||||
|
if password != "" {
|
||||||
|
return fmt.Sprintf("echo '%s' | sudo -S %s", EscapeSingleQuotes(password), command)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("sudo %s", command)
|
||||||
|
case ToolDoas:
|
||||||
|
return fmt.Sprintf("doas sh -c '%s'", EscapeSingleQuotes(command))
|
||||||
|
case ToolRun0:
|
||||||
|
return fmt.Sprintf("run0 sh -c '%s'", EscapeSingleQuotes(command))
|
||||||
|
default:
|
||||||
|
return failingShell(fmt.Errorf("unsupported privilege tool: %q", t))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecCommand builds an exec.Cmd that runs `command` as root via the
|
||||||
|
// detected tool. Detection errors surface at Run() time as a failing
|
||||||
|
// command writing a clear error to stderr.
|
||||||
|
func ExecCommand(ctx context.Context, password, command string) *exec.Cmd {
|
||||||
|
return exec.CommandContext(ctx, "bash", "-c", MakeCommand(password, command))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ExecArgv builds an exec.Cmd that runs argv as root via the detected tool.
|
||||||
|
// No stdin password is supplied; callers relying on non-interactive success
|
||||||
|
// should ensure cached credentials are present (see CheckCached).
|
||||||
|
func ExecArgv(ctx context.Context, argv ...string) *exec.Cmd {
|
||||||
|
if len(argv) == 0 {
|
||||||
|
return exec.CommandContext(ctx, "bash", "-c", failingShell(fmt.Errorf("privesc.ExecArgv: argv must not be empty")))
|
||||||
|
}
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return exec.CommandContext(ctx, "bash", "-c", failingShell(err))
|
||||||
|
}
|
||||||
|
|
||||||
|
switch t {
|
||||||
|
case ToolSudo, ToolDoas:
|
||||||
|
return exec.CommandContext(ctx, string(t), argv...)
|
||||||
|
case ToolRun0:
|
||||||
|
return exec.CommandContext(ctx, "run0", argv...)
|
||||||
|
default:
|
||||||
|
return exec.CommandContext(ctx, "bash", "-c", failingShell(fmt.Errorf("unsupported privilege tool: %q", t)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func failingShell(err error) string {
|
||||||
|
return fmt.Sprintf("printf 'privesc: %%s\\n' '%s' >&2; exit 1", EscapeSingleQuotes(err.Error()))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckCached runs a non-interactive credential probe. Returns nil if the
|
||||||
|
// tool will run commands without prompting (cached credentials, nopass, or
|
||||||
|
// polkit rule).
|
||||||
|
func CheckCached(ctx context.Context) error {
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
switch t {
|
||||||
|
case ToolSudo:
|
||||||
|
cmd = exec.CommandContext(ctx, "sudo", "-n", "true")
|
||||||
|
case ToolDoas:
|
||||||
|
cmd = exec.CommandContext(ctx, "doas", "-n", "true")
|
||||||
|
case ToolRun0:
|
||||||
|
cmd = exec.CommandContext(ctx, "run0", "--no-ask-password", "true")
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unsupported privilege tool: %q", t)
|
||||||
|
}
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ClearCache invalidates any cached credentials. No-op for tools that do
|
||||||
|
// not expose a cache-clear operation.
|
||||||
|
func ClearCache(ctx context.Context) error {
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switch t {
|
||||||
|
case ToolSudo:
|
||||||
|
return exec.CommandContext(ctx, "sudo", "-k").Run()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidateWithAskpass validates cached credentials using an askpass helper
|
||||||
|
// script. Only sudo supports this mechanism; the TUI uses it to trigger
|
||||||
|
// fingerprint authentication via PAM.
|
||||||
|
func ValidateWithAskpass(ctx context.Context, askpassScript string) error {
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t != ToolSudo {
|
||||||
|
return fmt.Errorf("askpass validation requires sudo (detected: %s)", t)
|
||||||
|
}
|
||||||
|
cmd := exec.CommandContext(ctx, "sudo", "-A", "-v")
|
||||||
|
cmd.Env = append(os.Environ(), fmt.Sprintf("SUDO_ASKPASS=%s", askpassScript))
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ValidatePassword validates the given password. Only sudo supports this
|
||||||
|
// (via `sudo -S -v`); for other tools the caller should fall back to
|
||||||
|
// CheckCached.
|
||||||
|
func ValidatePassword(ctx context.Context, password string) error {
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if t != ToolSudo {
|
||||||
|
return fmt.Errorf("password validation requires sudo (detected: %s)", t)
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd := exec.CommandContext(ctx, "sudo", "-S", "-v")
|
||||||
|
stdin, err := cmd.StdinPipe()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := cmd.Start(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if _, err := fmt.Fprintf(stdin, "%s\n", password); err != nil {
|
||||||
|
stdin.Close()
|
||||||
|
_ = cmd.Wait()
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
stdin.Close()
|
||||||
|
return cmd.Wait()
|
||||||
|
}
|
||||||
|
|
||||||
|
// QuoteArgsForShell wraps each argv element in single quotes so the result
|
||||||
|
// can be safely passed to bash -c.
|
||||||
|
func QuoteArgsForShell(argv []string) string {
|
||||||
|
parts := make([]string, len(argv))
|
||||||
|
for i, a := range argv {
|
||||||
|
parts[i] = "'" + EscapeSingleQuotes(a) + "'"
|
||||||
|
}
|
||||||
|
return strings.Join(parts, " ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run invokes argv with privilege escalation. When the tool supports stdin
|
||||||
|
// passwords and password is non-empty, the password is piped in. Otherwise
|
||||||
|
// argv is invoked directly, which may prompt on a TTY.
|
||||||
|
// Stdout and Stderr are inherited from the current process.
|
||||||
|
func Run(ctx context.Context, password string, argv ...string) error {
|
||||||
|
if len(argv) == 0 {
|
||||||
|
return fmt.Errorf("privesc.Run: argv must not be empty")
|
||||||
|
}
|
||||||
|
t, err := Detect()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cmd *exec.Cmd
|
||||||
|
switch {
|
||||||
|
case t == ToolSudo && password != "":
|
||||||
|
cmd = ExecCommand(ctx, password, QuoteArgsForShell(argv))
|
||||||
|
default:
|
||||||
|
cmd = ExecArgv(ctx, argv...)
|
||||||
|
}
|
||||||
|
cmd.Stdout = os.Stdout
|
||||||
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
// stdinIsTTY reports whether stdin is a character device (interactive
|
||||||
|
// terminal) rather than a pipe or file.
|
||||||
|
func stdinIsTTY() bool {
|
||||||
|
fi, err := os.Stdin.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return (fi.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// PromptCLI interactively prompts the user to pick a privilege tool when more
|
||||||
|
// than one is installed and $DMS_PRIVESC is not set. If stdin is not a TTY,
|
||||||
|
// or only one tool is available, or the env var is set, the detected tool is
|
||||||
|
// returned without any prompt.
|
||||||
|
//
|
||||||
|
// The prompt is written to out (typically os.Stdout/os.Stderr) and input is
|
||||||
|
// read from in. EOF or empty input selects the first option.
|
||||||
|
func PromptCLI(out io.Writer, in io.Reader) (Tool, error) {
|
||||||
|
if userSelected {
|
||||||
|
return Detect()
|
||||||
|
}
|
||||||
|
if _, envSet := EnvOverride(); envSet {
|
||||||
|
return Detect()
|
||||||
|
}
|
||||||
|
|
||||||
|
tools := AvailableTools()
|
||||||
|
switch len(tools) {
|
||||||
|
case 0:
|
||||||
|
return "", fmt.Errorf("no supported privilege tool (sudo/doas/run0) found on PATH")
|
||||||
|
case 1:
|
||||||
|
if err := SetTool(tools[0]); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return tools[0], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stdinIsTTY() {
|
||||||
|
return Detect()
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(out, "Multiple privilege escalation tools detected:")
|
||||||
|
for i, t := range tools {
|
||||||
|
fmt.Fprintf(out, " [%d] %s\n", i+1, t.Name())
|
||||||
|
}
|
||||||
|
fmt.Fprintf(out, "Choose one [1-%d] (default 1, or set %s=<tool> to skip): ", len(tools), EnvVar)
|
||||||
|
|
||||||
|
reader := bufio.NewReader(in)
|
||||||
|
line, err := reader.ReadString('\n')
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
return "", fmt.Errorf("failed to read selection: %w", err)
|
||||||
|
}
|
||||||
|
line = strings.TrimSpace(line)
|
||||||
|
|
||||||
|
idx := 1
|
||||||
|
if line != "" {
|
||||||
|
n, convErr := strconv.Atoi(line)
|
||||||
|
if convErr != nil || n < 1 || n > len(tools) {
|
||||||
|
return "", fmt.Errorf("invalid selection %q", line)
|
||||||
|
}
|
||||||
|
idx = n
|
||||||
|
}
|
||||||
|
|
||||||
|
chosen := tools[idx-1]
|
||||||
|
if err := SetTool(chosen); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return chosen, nil
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ package tui
|
|||||||
import (
|
import (
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/deps"
|
||||||
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/distros"
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
"github.com/charmbracelet/bubbles/spinner"
|
"github.com/charmbracelet/bubbles/spinner"
|
||||||
"github.com/charmbracelet/bubbles/textinput"
|
"github.com/charmbracelet/bubbles/textinput"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
@@ -42,6 +43,9 @@ type Model struct {
|
|||||||
sudoPassword string
|
sudoPassword string
|
||||||
existingConfigs []ExistingConfigInfo
|
existingConfigs []ExistingConfigInfo
|
||||||
fingerprintFailed bool
|
fingerprintFailed bool
|
||||||
|
|
||||||
|
availablePrivesc []privesc.Tool
|
||||||
|
selectedPrivesc int
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewModel(version string, logFilePath string) Model {
|
func NewModel(version string, logFilePath string) Model {
|
||||||
@@ -147,6 +151,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m.updateGentooUseFlagsState(msg)
|
return m.updateGentooUseFlagsState(msg)
|
||||||
case StateGentooGCCCheck:
|
case StateGentooGCCCheck:
|
||||||
return m.updateGentooGCCCheckState(msg)
|
return m.updateGentooGCCCheckState(msg)
|
||||||
|
case StateSelectPrivesc:
|
||||||
|
return m.updateSelectPrivescState(msg)
|
||||||
case StateAuthMethodChoice:
|
case StateAuthMethodChoice:
|
||||||
return m.updateAuthMethodChoiceState(msg)
|
return m.updateAuthMethodChoiceState(msg)
|
||||||
case StateFingerprintAuth:
|
case StateFingerprintAuth:
|
||||||
@@ -189,6 +195,8 @@ func (m Model) View() string {
|
|||||||
return m.viewGentooUseFlags()
|
return m.viewGentooUseFlags()
|
||||||
case StateGentooGCCCheck:
|
case StateGentooGCCCheck:
|
||||||
return m.viewGentooGCCCheck()
|
return m.viewGentooGCCCheck()
|
||||||
|
case StateSelectPrivesc:
|
||||||
|
return m.viewSelectPrivesc()
|
||||||
case StateAuthMethodChoice:
|
case StateAuthMethodChoice:
|
||||||
return m.viewAuthMethodChoice()
|
return m.viewAuthMethodChoice()
|
||||||
case StateFingerprintAuth:
|
case StateFingerprintAuth:
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ const (
|
|||||||
StateDependencyReview
|
StateDependencyReview
|
||||||
StateGentooUseFlags
|
StateGentooUseFlags
|
||||||
StateGentooGCCCheck
|
StateGentooGCCCheck
|
||||||
|
StateSelectPrivesc
|
||||||
StateAuthMethodChoice
|
StateAuthMethodChoice
|
||||||
StateFingerprintAuth
|
StateFingerprintAuth
|
||||||
StatePasswordPrompt
|
StatePasswordPrompt
|
||||||
|
|||||||
@@ -180,16 +180,7 @@ func (m Model) updateDependencyReviewState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Check if fingerprint is enabled
|
return m.enterAuthPhase()
|
||||||
if checkFingerprintEnabled() {
|
|
||||||
m.state = StateAuthMethodChoice
|
|
||||||
m.selectedConfig = 0 // Default to fingerprint
|
|
||||||
return m, nil
|
|
||||||
} else {
|
|
||||||
m.state = StatePasswordPrompt
|
|
||||||
m.passwordInput.Focus()
|
|
||||||
return m, nil
|
|
||||||
}
|
|
||||||
case "esc":
|
case "esc":
|
||||||
m.state = StateSelectWindowManager
|
m.state = StateSelectWindowManager
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|||||||
@@ -56,14 +56,7 @@ func (m Model) updateGentooUseFlagsState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
m.state = StateGentooGCCCheck
|
m.state = StateGentooGCCCheck
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
if checkFingerprintEnabled() {
|
return m.enterAuthPhase()
|
||||||
m.state = StateAuthMethodChoice
|
|
||||||
m.selectedConfig = 0
|
|
||||||
} else {
|
|
||||||
m.state = StatePasswordPrompt
|
|
||||||
m.passwordInput.Focus()
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
if keyMsg, ok := msg.(tea.KeyMsg); ok {
|
||||||
@@ -75,14 +68,7 @@ func (m Model) updateGentooUseFlagsState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if m.selectedWM == 1 {
|
if m.selectedWM == 1 {
|
||||||
return m, m.checkGCCVersion()
|
return m, m.checkGCCVersion()
|
||||||
}
|
}
|
||||||
if checkFingerprintEnabled() {
|
return m.enterAuthPhase()
|
||||||
m.state = StateAuthMethodChoice
|
|
||||||
m.selectedConfig = 0
|
|
||||||
} else {
|
|
||||||
m.state = StatePasswordPrompt
|
|
||||||
m.passwordInput.Focus()
|
|
||||||
}
|
|
||||||
return m, nil
|
|
||||||
case "esc":
|
case "esc":
|
||||||
m.state = StateDependencyReview
|
m.state = StateDependencyReview
|
||||||
return m, nil
|
return m, nil
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -274,8 +275,7 @@ func (m Model) delayThenReturn() tea.Cmd {
|
|||||||
|
|
||||||
func (m Model) tryFingerprint() tea.Cmd {
|
func (m Model) tryFingerprint() tea.Cmd {
|
||||||
return func() tea.Msg {
|
return func() tea.Msg {
|
||||||
clearCmd := exec.Command("sudo", "-k")
|
_ = privesc.ClearCache(context.Background())
|
||||||
clearCmd.Run()
|
|
||||||
|
|
||||||
tmpDir := os.TempDir()
|
tmpDir := os.TempDir()
|
||||||
askpassScript := filepath.Join(tmpDir, fmt.Sprintf("danklinux-fp-%d.sh", time.Now().UnixNano()))
|
askpassScript := filepath.Join(tmpDir, fmt.Sprintf("danklinux-fp-%d.sh", time.Now().UnixNano()))
|
||||||
@@ -289,15 +289,9 @@ func (m Model) tryFingerprint() tea.Cmd {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 15*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "sudo", "-A", "-v")
|
if err := privesc.ValidateWithAskpass(ctx, askpassScript); err != nil {
|
||||||
cmd.Env = append(os.Environ(), fmt.Sprintf("SUDO_ASKPASS=%s", askpassScript))
|
|
||||||
|
|
||||||
err := cmd.Run()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
return passwordValidMsg{password: "", valid: false}
|
return passwordValidMsg{password: "", valid: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
return passwordValidMsg{password: "", valid: true}
|
return passwordValidMsg{password: "", valid: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -307,32 +301,9 @@ func (m Model) validatePassword(password string) tea.Cmd {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
cmd := exec.CommandContext(ctx, "sudo", "-S", "-v")
|
if err := privesc.ValidatePassword(ctx, password); err != nil {
|
||||||
|
|
||||||
stdin, err := cmd.StdinPipe()
|
|
||||||
if err != nil {
|
|
||||||
return passwordValidMsg{password: "", valid: false}
|
return passwordValidMsg{password: "", valid: false}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
|
||||||
return passwordValidMsg{password: "", valid: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = fmt.Fprintf(stdin, "%s\n", password)
|
|
||||||
stdin.Close()
|
|
||||||
if err != nil {
|
|
||||||
return passwordValidMsg{password: "", valid: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
err = cmd.Wait()
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
if ctx.Err() == context.DeadlineExceeded {
|
|
||||||
return passwordValidMsg{password: "", valid: false}
|
|
||||||
}
|
|
||||||
return passwordValidMsg{password: "", valid: false}
|
|
||||||
}
|
|
||||||
|
|
||||||
return passwordValidMsg{password: password, valid: true}
|
return passwordValidMsg{password: password, valid: true}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
133
core/internal/tui/views_privesc.go
Normal file
133
core/internal/tui/views_privesc.go
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
package tui
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/AvengeMedia/DankMaterialShell/core/internal/privesc"
|
||||||
|
tea "github.com/charmbracelet/bubbletea"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m Model) viewSelectPrivesc() string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
b.WriteString(m.renderBanner())
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString(m.styles.Title.Render("Privilege Escalation Tool"))
|
||||||
|
b.WriteString("\n\n")
|
||||||
|
b.WriteString(m.styles.Normal.Render("Multiple privilege tools are available. Choose one for installation:"))
|
||||||
|
b.WriteString("\n\n")
|
||||||
|
|
||||||
|
for i, t := range m.availablePrivesc {
|
||||||
|
label := fmt.Sprintf("%s — %s", t.Name(), privescToolDescription(t))
|
||||||
|
switch i {
|
||||||
|
case m.selectedPrivesc:
|
||||||
|
b.WriteString(m.styles.SelectedOption.Render("▶ " + label))
|
||||||
|
default:
|
||||||
|
b.WriteString(m.styles.Normal.Render(" " + label))
|
||||||
|
}
|
||||||
|
b.WriteString("\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
b.WriteString("\n")
|
||||||
|
b.WriteString(m.styles.Subtle.Render(fmt.Sprintf("Set %s=<tool> to skip this prompt in future runs.", privesc.EnvVar)))
|
||||||
|
b.WriteString("\n\n")
|
||||||
|
b.WriteString(m.styles.Subtle.Render("↑/↓: Navigate, Enter: Select, Esc: Back"))
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m Model) updateSelectPrivescState(msg tea.Msg) (tea.Model, tea.Cmd) {
|
||||||
|
keyMsg, ok := msg.(tea.KeyMsg)
|
||||||
|
if !ok {
|
||||||
|
return m, m.listenForLogs()
|
||||||
|
}
|
||||||
|
|
||||||
|
switch keyMsg.String() {
|
||||||
|
case "up":
|
||||||
|
if m.selectedPrivesc > 0 {
|
||||||
|
m.selectedPrivesc--
|
||||||
|
}
|
||||||
|
case "down":
|
||||||
|
if m.selectedPrivesc < len(m.availablePrivesc)-1 {
|
||||||
|
m.selectedPrivesc++
|
||||||
|
}
|
||||||
|
case "enter":
|
||||||
|
chosen := m.availablePrivesc[m.selectedPrivesc]
|
||||||
|
if err := privesc.SetTool(chosen); err != nil {
|
||||||
|
m.err = fmt.Errorf("failed to select %s: %w", chosen.Name(), err)
|
||||||
|
m.state = StateError
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m.routeToAuthAfterPrivesc()
|
||||||
|
case "esc":
|
||||||
|
m.state = StateDependencyReview
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func privescToolDescription(t privesc.Tool) string {
|
||||||
|
switch t {
|
||||||
|
case privesc.ToolSudo:
|
||||||
|
return "classic sudo (supports password prompt in this installer)"
|
||||||
|
case privesc.ToolDoas:
|
||||||
|
return "OpenBSD-style doas (requires persist or nopass in /etc/doas.conf)"
|
||||||
|
case privesc.ToolRun0:
|
||||||
|
return "systemd run0 (authenticated via polkit)"
|
||||||
|
default:
|
||||||
|
return string(t)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// routeToAuthAfterPrivesc advances from the privesc-selection screen to the
|
||||||
|
// right auth flow. Sudo goes through the fingerprint/password path; doas and
|
||||||
|
// run0 skip password entry and proceed to install.
|
||||||
|
func (m Model) routeToAuthAfterPrivesc() (tea.Model, tea.Cmd) {
|
||||||
|
tool, err := privesc.Detect()
|
||||||
|
if err != nil {
|
||||||
|
m.err = err
|
||||||
|
m.state = StateError
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if tool == privesc.ToolSudo {
|
||||||
|
if checkFingerprintEnabled() {
|
||||||
|
m.state = StateAuthMethodChoice
|
||||||
|
m.selectedConfig = 0
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
m.state = StatePasswordPrompt
|
||||||
|
m.passwordInput.Focus()
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
m.sudoPassword = ""
|
||||||
|
m.packageProgress = packageInstallProgressMsg{}
|
||||||
|
m.state = StateInstallingPackages
|
||||||
|
m.isLoading = true
|
||||||
|
return m, tea.Batch(m.spinner.Tick, m.installPackages())
|
||||||
|
}
|
||||||
|
|
||||||
|
// enterAuthPhase is called when dependency review (or the Gentoo screens)
|
||||||
|
// finish. It either routes directly to the sudo/fingerprint flow or shows
|
||||||
|
// the privesc-tool selection screen when multiple tools are available and
|
||||||
|
// no $DMS_PRIVESC override is set.
|
||||||
|
func (m Model) enterAuthPhase() (tea.Model, tea.Cmd) {
|
||||||
|
tools := privesc.AvailableTools()
|
||||||
|
_, envSet := privesc.EnvOverride()
|
||||||
|
|
||||||
|
if len(tools) == 0 {
|
||||||
|
m.err = fmt.Errorf("no supported privilege tool (sudo/doas/run0) found on PATH")
|
||||||
|
m.state = StateError
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if envSet || len(tools) == 1 {
|
||||||
|
return m.routeToAuthAfterPrivesc()
|
||||||
|
}
|
||||||
|
|
||||||
|
m.availablePrivesc = tools
|
||||||
|
m.selectedPrivesc = 0
|
||||||
|
m.state = StateSelectPrivesc
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user