//go:build linux package installer import ( "fmt" "os" "os/exec" "strings" "gitgud.io/mike/mpv-manager/pkg/constants" "gitgud.io/mike/mpv-manager/pkg/log" ) // InstallMPVViaPackage installs MPV using the system package manager. func (li *LinuxInstaller) InstallMPVViaPackage(installUOSC bool) error { switch li.Platform.DistroFamily { case "debian": return li.installViaAPT(installUOSC) case "rhel": return li.installViaDNF(installUOSC) case "arch": return li.installViaPacman(installUOSC) default: return fmt.Errorf("unsupported distro family: %s", li.Platform.DistroFamily) } } // InstallCelluloidViaPackage installs Celluloid using the system package manager. func (li *LinuxInstaller) InstallCelluloidViaPackage() error { fmt.Println("Installing Celluloid via package manager...") fmt.Println("Note: You may be prompted for your password.") prefix := li.getPrivilegePrefix() switch li.Platform.DistroFamily { case "debian": fmt.Println("Updating package list...") args := append(prefix, "apt", "update") cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { return fmt.Errorf("apt update failed: %w", err) } fmt.Println("Installing required dependencies...") args = append(prefix, "apt", "install", "-y", "software-properties-common") cmd = exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin _ = cmd.Run() fmt.Println("Adding Celluloid PPA...") args = append(prefix, "add-apt-repository", "-y", "ppa:xuzhen666/gnome-mpv") cmd = exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin _ = cmd.Run() fmt.Println("Updating package list...") args = append(prefix, "apt", "update") cmd = exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin _ = cmd.Run() fmt.Println("Installing Celluloid...") args = append(prefix, "apt", "install", "-y", constants.PackageCelluloid) cmd = exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { return fmt.Errorf("apt install failed: %w", err) } case "rhel": fmt.Println("Installing Celluloid via dnf...") args := append(prefix, "dnf", "install", "-y", constants.PackageCelluloid) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { return fmt.Errorf("dnf install failed: %w", err) } case "arch": fmt.Println("Installing Celluloid via pacman...") args := append(prefix, "pacman", "-S", "--noconfirm", constants.PackageCelluloid) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { return fmt.Errorf("pacman install failed: %w", err) } default: return fmt.Errorf("unsupported distro family: %s", li.Platform.DistroFamily) } return nil } // installViaAPT installs MPV using apt (Debian/Ubuntu). func (li *LinuxInstaller) installViaAPT(installUOSC bool) error { fmt.Println("Installing MPV via apt...") fmt.Println("Note: You may be prompted for your password.") prefix := li.getPrivilegePrefix() args := append(prefix, "apt", "update") cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin if err := cmd.Run(); err != nil { return fmt.Errorf("apt update failed: %w", err) } args = append(prefix, "apt", "install", "-y", constants.PackageMPV) cmd = exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin err := cmd.Run() if err != nil { return fmt.Errorf("apt install failed: %w", err) } return nil } // installViaDNF installs MPV using dnf (Fedora/RHEL). func (li *LinuxInstaller) installViaDNF(installUOSC bool) error { fmt.Println("Installing MPV via dnf...") fmt.Println("Note: You may be prompted for your password.") prefix := li.getPrivilegePrefix() args := append(prefix, "dnf", "install", "-y", constants.PackageMPV) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin err := cmd.Run() if err != nil { return fmt.Errorf("dnf install failed: %w", err) } return nil } // installViaPacman installs MPV using pacman (Arch Linux). func (li *LinuxInstaller) installViaPacman(installUOSC bool) error { fmt.Println("Installing MPV via pacman...") fmt.Println("Note: You may be prompted for your password.") prefix := li.getPrivilegePrefix() args := append(prefix, "pacman", "-S", "--noconfirm", constants.PackageMPV) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin err := cmd.Run() if err != nil { return fmt.Errorf("pacman install failed: %w", err) } return nil } // UninstallViaPackage uninstalls an application using the system package manager. func (li *LinuxInstaller) UninstallViaPackage(appName string) error { prefix := li.getPrivilegePrefix() switch li.Platform.DistroFamily { case "debian": args := append(prefix, "apt", "remove", "-y", appName) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() case "rhel": args := append(prefix, "dnf", "remove", "-y", appName) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() case "arch": args := append(prefix, "pacman", "-Rns", "--noconfirm", appName) cmd := exec.Command(args[0], args[1:]...) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr return cmd.Run() default: return fmt.Errorf("unsupported distro family: %s", li.Platform.DistroFamily) } } // InstallMPVViaPackageWithOutput installs MPV using the system package manager with output streaming. func (li *LinuxInstaller) InstallMPVViaPackageWithOutput(cr *CommandRunner, uiType string, isUpdate bool) error { family := li.Platform.DistroFamily if family == "" { family = detectLinuxFamily() } packageName := constants.PackageMPV if isUpdate { cr.outputChan <- fmt.Sprintf("Updating MPV via package manager (family: %s)...", family) pm := NewPackageManager(family) // Build commands WITHOUT sudo prefix - RunSudoCommand handles authentication updateCmd := pm.BuildUpdateCommand(false) upgradeCmd := pm.BuildUpgradeCommand(packageName, false) if cr != nil { cr.outputChan <- "Updating package metadata..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(updateCmd, " ")) } if err := cr.RunSudoCommand(updateCmd[0], updateCmd[1:]...); err != nil { log.Warn("Failed to update package metadata, continuing...") } if cr != nil { cr.outputChan <- "Updating MPV..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(upgradeCmd, " ")) } if err := cr.RunSudoCommand(upgradeCmd[0], upgradeCmd[1:]...); err != nil { return err } cr.outputChan <- constants.MsgConfigNotOverwritten return nil } cr.outputChan <- fmt.Sprintf("Installing MPV via package manager (family: %s)...", family) pm := NewPackageManager(family) // Build command WITHOUT sudo prefix - RunSudoCommand handles authentication installCmd := pm.BuildInstallCommand(packageName, false) if cr != nil { cr.outputChan <- "Running: sudo " + strings.Join(installCmd, " ") } if err := cr.RunSudoCommand(installCmd[0], installCmd[1:]...); err != nil { return err } if uiType != constants.UITypeNone { homeDir, err := os.UserHomeDir() if err != nil { log.Warn(fmt.Sprintf("Failed to get home directory: %v", err)) } else { configDir := GetMPVConfigDirFromHome(homeDir) InstallUISafely(li.Installer, cr, configDir, uiType) } } // Install MPV config with platform-specific defaults if err := InstallMPVConfigWithPlatformDefaults(li.Installer, cr, li.Platform.OSType, li.Platform.GPUInfo.Brand); err != nil { cr.outputChan <- fmt.Sprintf("Warning: Failed to install MPV config: %v", err) } return nil } // InstallCelluloidViaPackageWithOutput installs Celluloid using the system package manager with output streaming. func (li *LinuxInstaller) InstallCelluloidViaPackageWithOutput(cr *CommandRunner, isUpdate bool) error { family := li.Platform.DistroFamily if family == "" { family = detectLinuxFamily() } packageName := constants.PackageCelluloid if isUpdate { cr.outputChan <- fmt.Sprintf("Updating Celluloid via package manager (family: %s)...", family) pm := NewPackageManager(family) // Build commands WITHOUT sudo prefix - RunSudoCommand handles authentication updateCmd := pm.BuildUpdateCommand(false) upgradeCmd := pm.BuildUpgradeCommand(packageName, false) if cr != nil { cr.outputChan <- "Updating package metadata..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(updateCmd, " ")) } if err := cr.RunSudoCommand(updateCmd[0], updateCmd[1:]...); err != nil { log.Warn("Failed to update package metadata, continuing...") } if cr != nil { cr.outputChan <- "Updating Celluloid..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(upgradeCmd, " ")) } if err := cr.RunSudoCommand(upgradeCmd[0], upgradeCmd[1:]...); err != nil { return err } return nil } cr.outputChan <- fmt.Sprintf("Installing Celluloid via package manager (family: %s)...", family) pm := NewPackageManager(family) // Build commands WITHOUT sudo prefix - RunSudoCommand handles authentication updateCmd := pm.BuildUpdateCommand(false) installCmd := pm.BuildInstallCommand(packageName, false) if cr != nil { cr.outputChan <- "Updating package list..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(updateCmd, " ")) } if err := cr.RunSudoCommand(updateCmd[0], updateCmd[1:]...); err != nil { log.Warn("Failed to update package list, continuing...") } if cr != nil { cr.outputChan <- "Installing Celluloid..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(installCmd, " ")) } if err := cr.RunSudoCommand(installCmd[0], installCmd[1:]...); err != nil { return err } // Install MPV config with platform-specific defaults if err := InstallMPVConfigWithPlatformDefaults(li.Installer, cr, li.Platform.OSType, li.Platform.GPUInfo.Brand); err != nil { log.Warn(fmt.Sprintf("Failed to install MPV config: %v", err)) } if err := li.ConfigureCelluloidPackageWithOutput(cr); err != nil { log.Warn(fmt.Sprintf("Failed to configure Celluloid: %v", err)) } return nil } // isMPVInstall checks if the given install ID corresponds to an MPV installation. func isMPVInstall(installID string) bool { mpvInstalls := []string{constants.FlatpakMPV, constants.PackageMPV, constants.FlatpakMPV} for _, mpvID := range mpvInstalls { if installID == mpvID { return true } } return false } // detectLinuxFamily detects the Linux distribution family from /etc/os-release. func detectLinuxFamily() string { if data, err := os.ReadFile("/etc/os-release"); err == nil { content := string(data) if strings.Contains(content, "debian") || strings.Contains(content, "ubuntu") { return "debian" } else if strings.Contains(content, "rhel") || strings.Contains(content, "fedora") { return "rhel" } else if strings.Contains(content, "arch") { return "arch" } } return "" } // getPackageManagerName returns the name of the package manager for the current distro. func (li *LinuxInstaller) getPackageManagerName() string { family := li.Platform.DistroFamily if family == "" { family = detectLinuxFamily() } if family == "arch" { return "pacman" } else if family == "debian" { return "apt" } else if family == "rhel" { return "dnf" } return "package-manager" } // UninstallViaPackageWithOutput uninstalls an application using the system package manager with output streaming. func (li *LinuxInstaller) UninstallViaPackageWithOutput(cr *CommandRunner, appName string) error { cr.outputChan <- fmt.Sprintf("Uninstalling %s via package manager...", appName) packageManager := NewPackageManager(li.Platform.DistroFamily) // Build command WITHOUT sudo prefix - RunSudoCommand handles authentication removeCmd := packageManager.BuildRemoveCommand(appName, false) if cr != nil { cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(removeCmd, " ")) } // Use RunSudoCommand which handles keyring password authentication if err := cr.RunSudoCommand(removeCmd[0], removeCmd[1:]...); err != nil { return fmt.Errorf("package remove failed: %w", err) } return nil }