Peercord/Peercord Installer Source/PeercordInstaller/Installers/Linux.cs
0% [█ █ █ █ █ █ █ █ █ █] 100% 29e61f07f2 Full source
2026-06-14 21:28:04 -05:00

293 lines
13 KiB
C#

using System;
using System.Diagnostics;
using System.IO;
using System.IO.Compression;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading.Tasks;
namespace PeercordInstaller.Installers
{
public class Linux
{
// TODO: Replace this with the direct link to your Peercord Linux .zip release
private const string DOWNLOAD_URL = "https://storage.mastercodeon.dev/Peercord%20Release/peercord-linux-x64.zip";
public void AppendLog(string text) => Utils.Instance.AppendLog(text);
public static string GetInstallPath()
{
try
{
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "peercord", "install_path.txt");
if (File.Exists(path))
{
return File.ReadAllText(path).Trim();
}
}
catch { }
return null;
}
public static void SetInstallPath(string targetDir)
{
try
{
string dirPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "peercord");
Directory.CreateDirectory(dirPath);
File.WriteAllText(Path.Combine(dirPath, "install_path.txt"), targetDir);
}
catch { }
}
public static void RemoveInstallPath()
{
try
{
string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "peercord", "install_path.txt");
if (File.Exists(path))
{
File.Delete(path);
}
}
catch { }
}
public async Task<string> InstallPeercordLinuxAsync(string targetDir)
{
Utils.Instance.SetProgress(10, "Downloading Peercord...");
string zipPath = Path.Combine(Path.GetTempPath(), "Peercord.zip");
await DownloadFileAsync(DOWNLOAD_URL, zipPath);
Utils.Instance.SetProgress(70, "Extracting files...");
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Extracting files to {targetDir}...\n");
if (!Directory.Exists(targetDir))
{
Directory.CreateDirectory(targetDir);
}
// Extract zip, handling Windows-style backslashes in zip entries on Linux
using (ZipArchive archive = ZipFile.OpenRead(zipPath))
{
int totalFiles = archive.Entries.Count;
int extracted = 0;
foreach (ZipArchiveEntry entry in archive.Entries)
{
// Replace backslashes with forward slashes for Linux compatibility
string fixedName = entry.FullName.Replace('\\', '/');
string destinationPath = Path.GetFullPath(Path.Combine(targetDir, fixedName));
// Prevent ZipSlip vulnerability
if (!destinationPath.StartsWith(Path.GetFullPath(targetDir), StringComparison.Ordinal))
continue;
if (fixedName.EndsWith("/"))
{
// It's a directory
Directory.CreateDirectory(destinationPath);
}
else
{
// It's a file, ensure its parent directory exists
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath));
entry.ExtractToFile(destinationPath, true);
}
extracted++;
if (extracted % 50 == 0 || extracted == totalFiles)
{
int percent = 70 + (int)((extracted / (double)totalFiles) * 10); // 70 to 80
Utils.Instance.SetProgress(percent, "Extracting files...");
AppendLog($"\r[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [PROGRESS] Extracting... {extracted}/{totalFiles} files ({(extracted / (double)totalFiles):P0})");
}
}
}
AppendLog($"\n[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Extraction complete.\n");
File.Delete(zipPath);
// The executable is typically named after the package name in lowercase
string exePath = Path.Combine(targetDir, "peercord");
// Fallback in case it was capitalized by the build process
if (!File.Exists(exePath) && File.Exists(Path.Combine(targetDir, "Peercord")))
{
exePath = Path.Combine(targetDir, "Peercord");
}
Utils.Instance.SetProgress(80, "Configuring permissions...");
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Configuring execute permissions...\n");
// Recursively add execute permissions to the directory so internal Electron binaries (like chrome-sandbox) work
await Utils.RunCommandAsync("chmod", $"-R +x \"{targetDir}\"");
// Save installation record
SetInstallPath(targetDir);
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Peercord installed successfully.\n");
return exePath;
}
public async Task UninstallPeercordLinuxAsync(string targetDir)
{
Utils.Instance.SetProgress(10, "Preparing uninstallation...");
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Starting uninstallation from {targetDir}...\n");
Utils.Instance.SetProgress(30, "Removing shortcuts...");
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
if (string.IsNullOrEmpty(desktopPath) || !Directory.Exists(desktopPath))
{
desktopPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Desktop");
}
string desktopShortcut = Path.Combine(desktopPath, "peercord.desktop");
if (File.Exists(desktopShortcut))
{
File.Delete(desktopShortcut);
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Removed Desktop shortcut.\n");
}
string appMenuPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "applications", "peercord.desktop");
if (File.Exists(appMenuPath))
{
File.Delete(appMenuPath);
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Removed App Menu shortcut.\n");
}
Utils.Instance.SetProgress(50, "Removing files...");
if (Directory.Exists(targetDir))
{
Directory.Delete(targetDir, true);
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Removed installation directory.\n");
}
Utils.Instance.SetProgress(90, "Cleaning up system records...");
RemoveInstallPath();
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Removed installation records.\n");
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Uninstallation complete.\n");
Utils.Instance.SetProgress(100, "Done!");
}
public async Task CreateAppMenuShortcut(string targetExe, string targetDir)
{
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Creating App Menu shortcut...\n");
string applicationsPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".local", "share", "applications");
Directory.CreateDirectory(applicationsPath);
string iconPath = Path.Combine(targetDir, "resources", "app", "assets", "icon.png");
string desktopEntry = $@"[Desktop Entry]
Name=Peercord
Exec=""{targetExe}""
Icon={iconPath}
Type=Application
Terminal=false
Categories=Network;
";
string shortcutName = "peercord.desktop";
string appMenuPath = Path.Combine(applicationsPath, shortcutName);
await File.WriteAllTextAsync(appMenuPath, desktopEntry);
await Utils.RunCommandAsync("chmod", $"+x \"{appMenuPath}\"");
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] App Menu shortcut created.\n");
}
public async Task CreateDesktopShortcut(string targetExe, string targetDir)
{
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Creating Desktop shortcut...\n");
string iconPath = Path.Combine(targetDir, "resources", "app", "assets", "icon.png");
string desktopEntry = $@"[Desktop Entry]
Name=Peercord
Exec=""{targetExe}""
Icon={iconPath}
Type=Application
Terminal=false
Categories=Network;
";
string shortcutName = "peercord.desktop";
string desktopPath = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory);
if (string.IsNullOrEmpty(desktopPath) || !Directory.Exists(desktopPath))
{
desktopPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Desktop");
}
if (Directory.Exists(desktopPath))
{
string desktopShortcutPath = Path.Combine(desktopPath, shortcutName);
await File.WriteAllTextAsync(desktopShortcutPath, desktopEntry);
// Make it executable
await Utils.RunCommandAsync("chmod", $"+x \"{desktopShortcutPath}\"");
// Mark it as trusted for environments like GNOME
await Utils.RunCommandAsync("gio", $"set \"{desktopShortcutPath}\" metadata::trusted true");
// Mark it as trusted for XFCE (Kali Linux default)
try
{
byte[] fileBytes = await File.ReadAllBytesAsync(desktopShortcutPath);
using (SHA256 sha256 = SHA256.Create())
{
byte[] hashBytes = sha256.ComputeHash(fileBytes);
string hashString = BitConverter.ToString(hashBytes).Replace("-", "").ToLowerInvariant();
await Utils.RunCommandAsync("gio", $"set -t string \"{desktopShortcutPath}\" metadata::xfce-exe-checksum \"{hashString}\"");
}
}
catch (Exception ex)
{
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [ERROR] Failed to set XFCE trust metadata: {ex.Message}\n");
}
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Desktop shortcut created and marked as trusted.\n");
}
}
private async Task DownloadFileAsync(string url, string destination)
{
AppendLog($"[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Downloading Peercord from {url}...\n");
using var client = new HttpClient();
using var response = await client.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
response.EnsureSuccessStatusCode();
long? totalBytes = response.Content.Headers.ContentLength;
using var contentStream = await response.Content.ReadAsStreamAsync();
using var fileStream = new FileStream(destination, FileMode.Create, FileAccess.Write, FileShare.None, 8192, true);
var buffer = new byte[8192];
long totalRead = 0;
int bytesRead;
var stopwatch = Stopwatch.StartNew();
long lastReportTime = 0;
while ((bytesRead = await contentStream.ReadAsync(buffer, 0, buffer.Length)) > 0)
{
await fileStream.WriteAsync(buffer, 0, bytesRead);
totalRead += bytesRead;
if (stopwatch.ElapsedMilliseconds - lastReportTime > 500 || totalRead == totalBytes)
{
lastReportTime = stopwatch.ElapsedMilliseconds;
double speedMb = (totalRead / 1024.0 / 1024.0) / (stopwatch.ElapsedMilliseconds / 1000.0);
if (totalBytes.HasValue)
{
double percentage = (double)totalRead / totalBytes.Value * 100;
AppendLog($"\r[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [PROGRESS] Downloading... {percentage:F1}% ({speedMb:F2} MB/s)");
}
else
{
AppendLog($"\r[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [PROGRESS] Downloading... {totalRead / 1024.0 / 1024.0:F2} MB ({speedMb:F2} MB/s)");
}
}
}
AppendLog($"\n[{DateTime.Now:yyyy-MM-dd HH:mm:ss}] [INFO] Download complete.\n");
}
}
}