using AppScaffolding; using LibationFileManager; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; namespace LinuxConfigApp; internal class LinuxInterop : IInteropFunctions { //Different terminal apps possibly installed on a linux system // [0] console executable // [1] argument to set the concole's title // [2] argument to pass a command to be executed to the terminal static readonly string[][] consoleCommands = { new[] {"konsole", "--title", "-e"}, new[] {"gnome-terminal", "--title", "--"}, new[] {"mate-terminal", "--title", "-x"}, new[] {"xterm", "-T", "-e"}, }; public LinuxInterop() { } public LinuxInterop(params object[] values) { } public void SetFolderIcon(string image, string directory) => throw new PlatformNotSupportedException(); public void SetFolderIcon(byte[] imageJpegBytes, string directory) => throw new PlatformNotSupportedException(); public void DeleteFolderIcon(string directory) => throw new PlatformNotSupportedException(); public string ReleaseIdString => LibationScaffolding.ReleaseIdentifier.ToString() + (File.Exists("/usr/bin/apt") || File.Exists("/bin/apt") ? "_DEB" : "_RPM"); //only run the auto upgrader if the current app was installed from the //.deb or .rpm package. Try to detect this by checking if the symlink exists. public bool CanUpgrade => File.Exists("/usr/bin/libation") || File.Exists("/bin/libation"); public async Task InstallUpgradeAsync(string upgradeBundle) { if (string.IsNullOrWhiteSpace(upgradeBundle) || !File.Exists(upgradeBundle)) throw new FileNotFoundException("Upgrade bundle not found.", upgradeBundle); if (!TryResolvePackageManager(upgradeBundle, out var pkgExe, out var pkgArgs)) throw new PlatformNotSupportedException("Could not find apt, dnf, yum, or dnf5 to install the upgrade."); if (FindPkexec(out var pkexec)) { var psi = new ProcessStartInfo { FileName = pkexec, UseShellExecute = false, }; // pkexec requires an absolute path to the program on modern polkit. foreach (var a in new[] { pkgExe }.Concat(pkgArgs)) psi.ArgumentList.Add(a); if (string.Equals(Path.GetFileName(pkgExe), "apt", StringComparison.OrdinalIgnoreCase)) psi.Environment["DEBIAN_FRONTEND"] = "noninteractive"; var proc = Process.Start(psi); if (proc is null) throw new InvalidOperationException("Failed to start pkexec."); await proc.WaitForExitAsync(); if (proc.ExitCode != 0) throw new InvalidOperationException($"Package manager exited with code {proc.ExitCode}."); return; } // Terminal + runasroot.sh: completion cannot be tracked reliably; user must watch the window. var legacyArgs = string.Join(" ", pkgArgs); Serilog.Log.Logger.Warning("pkexec not found; launching install in a terminal. Completion cannot be verified automatically."); RunAsRoot(pkgExe, legacyArgs); } private static bool TryResolvePackageManager(string upgradeBundle, out string pkgExe, out string[] pkgArgs) { if (TryFirstExisting(out pkgExe, "/usr/bin/dnf5", "/bin/dnf5")) { pkgArgs = new[] { "install", "-y", upgradeBundle }; return true; } if (TryFirstExisting(out pkgExe, "/usr/bin/dnf", "/bin/dnf")) { pkgArgs = new[] { "install", "-y", upgradeBundle }; return true; } if (TryFirstExisting(out pkgExe, "/usr/bin/yum", "/bin/yum")) { pkgArgs = new[] { "install", "-y", upgradeBundle }; return true; } if (TryFirstExisting(out pkgExe, "/usr/bin/apt", "/bin/apt")) { pkgArgs = new[] { "install", "-y", "-o", "Dpkg::Options::=--force-confdef", "-o", "Dpkg::Options::=--force-confold", upgradeBundle }; return true; } pkgExe = ""; pkgArgs = Array.Empty(); return false; } private static bool TryFirstExisting(out string path, params string[] candidates) { foreach (var c in candidates) { if (File.Exists(c)) { path = c; return true; } } path = ""; return false; } private bool FindPkexec(out string? exePath) { if (File.Exists("/usr/bin/pkexec")) { exePath = "/usr/bin/pkexec"; return true; } if (File.Exists("/bin/pkexec")) { exePath = "/bin/pkexec"; return true; } exePath = null; return false; } public Process? RunAsRoot(string exe, string args) { //cribbed this script from VirtualBox's guest additions installer. //It's designed to launch the system's gui superuser password //prompt across multiple distributions and desktop environments. const string runasroot = "runasroot.sh"; string command = $"{exe ?? ""} {args ?? ""}".Trim(); foreach (var console in consoleCommands) { ProcessStartInfo psi = new() { FileName = console[0], UseShellExecute = false, ArgumentList = { console[1], $"Running '{exe}' as root", // console title console[2], "/bin/sh", Path.Combine(Configuration.ProcessDirectory, runasroot), //script file "Installing libation.deb", //command title command, // command to execute vis /bin/sh $"Please run '{command}' manually" // error message to display in the terminal } }; try { return Process.Start(psi); } catch { } } throw new PlatformNotSupportedException($"Could not start any of the supported terminals: {string.Join(", ", consoleCommands.Select(c => c[0]))}"); } }