//go:build linux package installer import ( "fmt" "strings" "gitgud.io/mike/mpv-manager/pkg/constants" "gitgud.io/mike/mpv-manager/pkg/log" "gitgud.io/mike/mpv-manager/pkg/platform" ) // InstallMPVViaPackageWithOutput installs MPV using the system package manager with output streaming. func (li *LinuxInstaller) InstallMPVViaPackageWithOutput(cr *CommandRunner, uiType string, isUpdate bool) error { preserveConfig, err := existingMPVConfig() if err != nil { return err } family := li.Platform.DistroFamily if family == "" { family = detectLinuxFamily() } packageName := constants.PackageMPV pm := NewPackageManager(family) installCmd := pm.BuildInstallCommand(packageName, false) if len(installCmd) == 0 { return fmt.Errorf("unsupported distro family %q: cannot build package manager install command; try the Flatpak method instead", family) } var preparedUI *PreparedUI if !isUpdate && uiType != constants.UITypeNone { configDir, err := GetMPVConfigDir() if err != nil { return err } preparedUI, err = li.PrepareUI(cr, configDir, uiType) if err != nil { return err } defer preparedUI.Close() } if err := cr.BeginCommit(); err != nil { return err } if isUpdate { cr.outputChan <- fmt.Sprintf("Updating MPV via package manager (family: %s)...", family) // Build commands WITHOUT sudo prefix - RunSudoCommand handles authentication updateCmd := pm.BuildUpdateCommand(false) upgradeCmd := pm.BuildUpgradeCommand(packageName, false) if len(updateCmd) == 0 || len(upgradeCmd) == 0 { return fmt.Errorf("unsupported distro family %q: cannot build package manager update commands", family) } notePacmanFullUpgrade(cr, family) if cr != nil { cr.outputChan <- "Updating package metadata..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(updateCmd, " ")) } if err := li.runSudoCommand(cr, 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 := li.runSudoCommand(cr, 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) notePacmanFullUpgrade(cr, family) if cr != nil { cr.outputChan <- "Running: sudo " + strings.Join(installCmd, " ") } if err := li.runSudoCommand(cr, installCmd[0], installCmd[1:]...); err != nil { return err } if preserveConfig { cr.outputChan <- constants.MsgConfigNotOverwritten } else if err := InstallMPVConfigWithPlatformDefaults(li.Installer, cr, li.Platform.OSType, li.Platform.GPUInfo); err != nil { return &PartialInstallError{Err: fmt.Errorf("install MPV config: %w", err)} } // The UI transaction reads the latest live config and applies its OSC // choice after fresh platform defaults have been installed. if preparedUI != nil { if err := preparedUI.Commit(cr); err != nil { return &PartialInstallError{Err: err} } } return nil } // InstallCelluloidViaPackageWithOutput installs Celluloid using the system package manager with output streaming. func (li *LinuxInstaller) InstallCelluloidViaPackageWithOutput(cr *CommandRunner, isUpdate bool) error { preserveConfig, err := existingMPVConfig() if err != nil { return err } if err := cr.BeginCommit(); err != nil { return err } 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 len(updateCmd) == 0 || len(upgradeCmd) == 0 { return fmt.Errorf("unsupported distro family %q: cannot build package manager update commands", family) } notePacmanFullUpgrade(cr, family) if cr != nil { cr.outputChan <- "Updating package metadata..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(updateCmd, " ")) } if err := li.runSudoCommand(cr, 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 := li.runSudoCommand(cr, 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 len(updateCmd) == 0 || len(installCmd) == 0 { return fmt.Errorf("unsupported distro family %q: cannot build package manager install command; try the Flatpak method instead", family) } notePacmanFullUpgrade(cr, family) if cr != nil { cr.outputChan <- "Updating package list..." cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(updateCmd, " ")) } if err := li.runSudoCommand(cr, 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 := li.runSudoCommand(cr, installCmd[0], installCmd[1:]...); err != nil { return err } if preserveConfig { cr.outputChan <- constants.MsgConfigNotOverwritten } else { // Install MPV config with platform-specific defaults if err := InstallMPVConfigWithPlatformDefaults(li.Installer, cr, li.Platform.OSType, li.Platform.GPUInfo); 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 { return installID == constants.FlatpakMPV || installID == constants.PackageMPV } // detectLinuxFamily detects the Linux distribution family from os-release. // Delegates to the canonical implementation in the platform package. func detectLinuxFamily() string { return platform.GetLinuxDistroFamily() } // notePacmanFullUpgrade emits a note about pacman's full-system-upgrade // behavior (-Syu), which is used for install/upgrade on Arch to avoid the // partial-upgrade pattern Arch warns against. func notePacmanFullUpgrade(cr *CommandRunner, family string) { if family == constants.PMFamilyArch && cr != nil { cr.outputChan <- "Note: pacman will refresh databases and perform a full system upgrade (-Syu), as recommended by Arch Linux." } } // 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 len(removeCmd) == 0 { return fmt.Errorf("unsupported distro family %q: cannot build package manager remove command", li.Platform.DistroFamily) } if cr != nil { cr.outputChan <- fmt.Sprintf("Running: sudo %s", strings.Join(removeCmd, " ")) } // Use RunSudoCommand which handles keyring password authentication if err := li.runSudoCommand(cr, removeCmd[0], removeCmd[1:]...); err != nil { return fmt.Errorf("package remove failed: %w", err) } return nil }