Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Generated Files/Secrets.Generated.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Auto-generated file - do not modify
namespace UniGetUI.Services
{
internal static partial class Secrets
{
public static partial string GetGitHubClientId() => "CLIENT_ID_UNSET";
public static partial string GetGitHubClientSecret() => "CLIENT_SECRET_UNSET";
public static partial string GetOpenSearchUsername() => "OPENSEARCH_USERNAME_UNSET";
public static partial string GetOpenSearchPassword() => "OPENSEARCH_PASSWORD_UNSET";
}
}
13 changes: 10 additions & 3 deletions src/UniGetUI.Avalonia/Infrastructure/AvaloniaAutoUpdater.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1120,12 +1120,19 @@ private static async Task DownloadInstallerAsync(
throw new InvalidOperationException($"Download URL is not allowed: {url}");
}

LogUpdateDebug($"Downloading installer from {url}");
// Apply GitHub URL acceleration if configured (security check uses original URL)
string acceleratedUrl = CoreTools.AccelerateDownloadUrl(new Uri(url))?.ToString() ?? url;

LogUpdateDebug(
acceleratedUrl != url
? $"Downloading installer from {acceleratedUrl} (accelerated from {url})"
: $"Downloading installer from {url}"
);
using HttpClient client = new(CreateHttpClientHandler(overrides));
client.Timeout = TimeSpan.FromSeconds(600);
client.DefaultRequestHeaders.UserAgent.ParseAdd(CoreData.UserAgentString);

HttpResponseMessage response = await client.GetAsync(url);
HttpResponseMessage response = await client.GetAsync(acceleratedUrl);
response.EnsureSuccessStatusCode();

using FileStream fs = new(destination, FileMode.OpenOrCreate);
Expand All @@ -1137,7 +1144,7 @@ private static async Task DownloadInstallerAsync(
// ------------------------------------------------------------------ HTTP client
private static HttpClientHandler CreateHttpClientHandler(UpdaterOverrides overrides)
{
var handler = new HttpClientHandler();
HttpClientHandler handler = CoreTools.GenericHttpClientParameters;
if (overrides.DisableTlsValidation)
{
LogUpdateWarn("Registry override: TLS certificate validation is disabled for updater requests.");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,13 @@ public partial class InternetViewModel : ViewModelBase

[ObservableProperty] private bool _isProxyEnabled;
[ObservableProperty] private bool _isProxyAuthEnabled;
[ObservableProperty] private bool _isGitHubAccelerationEnabled;

public InternetViewModel()
{
_isProxyEnabled = CoreSettings.Get(CoreSettings.K.EnableProxy);
_isProxyAuthEnabled = CoreSettings.Get(CoreSettings.K.EnableProxyAuth);
_isGitHubAccelerationEnabled = CoreSettings.Get(CoreSettings.K.EnableGitHubAcceleration);
}

public SettingsCard BuildCredentialsCard()
Expand Down Expand Up @@ -196,6 +198,12 @@ private void RefreshProxyAuthEnabled()
ApplyProxyToProcess();
}

[RelayCommand]
private void RefreshGitHubAccelerationEnabled()
{
IsGitHubAccelerationEnabled = CoreSettings.Get(CoreSettings.K.EnableGitHubAcceleration);
}

[RelayCommand]
private static void ApplyProxy() => ApplyProxyToProcess();

Expand Down
20 changes: 20 additions & 0 deletions src/UniGetUI.Avalonia/Views/Pages/SettingsPages/Internet.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,26 @@
Text="{t:Translate Wait for the device to be connected to the internet before attempting to do tasks that require internet connectivity.}"
StateChangedCommand="{Binding ShowRestartRequiredCommand}"
CornerRadius="8"/>

<Border Height="16"/>

<settings:TranslatedTextBlock Text="GitHub download acceleration"
FontWeight="SemiBold"
Margin="44,32,4,8"
automation:AutomationProperties.HeadingLevel="2"/>

<settings:CheckboxCard SettingName="EnableGitHubAcceleration"
Text="{t:Translate Accelerate GitHub downloads using a proxy or mirror service}"
Description="{t:Translate Text='Rewrites GitHub download URLs (github.com, raw.githubusercontent.com) through an accelerator service for faster downloads in restricted network environments'}"
StateChangedCommand="{Binding RefreshGitHubAccelerationEnabledCommand}"
CornerRadius="8,8,0,0"/>

<settings:TextboxCard SettingName="GitHubAcceleratorUrl"
Text="{t:Translate Accelerator server URL}"
Placeholder="https://xget.example.com"
CornerRadius="0,0,8,8"
BorderThickness="1,0,1,1"
IsEnabled="{Binding IsGitHubAccelerationEnabled}"/>
</StackPanel>
</ScrollViewer>

Expand Down
4 changes: 4 additions & 0 deletions src/UniGetUI.Core.Settings/SettingsEngine_Names.cs
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,8 @@ public enum K
DisableInstallerHostChangeWarning,
BunPreferLatestVersions,
TrayIconStyle,
EnableGitHubAcceleration,
GitHubAcceleratorUrl,

Test1,
Test2,
Expand Down Expand Up @@ -199,6 +201,8 @@ public static string ResolveKey(K key)
K.DisableInstallerHostChangeWarning => "DisableInstallerHostChangeWarning",
K.BunPreferLatestVersions => "BunPreferLatestVersions",
K.TrayIconStyle => "TrayIconStyle",
K.EnableGitHubAcceleration => "EnableGitHubAcceleration",
K.GitHubAcceleratorUrl => "GitHubAcceleratorUrl",

K.Test1 => "TestSetting1",
K.Test2 => "TestSetting2",
Expand Down
50 changes: 50 additions & 0 deletions src/UniGetUI.Core.Tools/Tools.cs
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,52 @@ static CoreTools()
LoadStaticTranslation();
}

/// <summary>
/// Accelerates a GitHub download URL by rewriting it through a configured accelerator
/// (e.g., a self-hosted xget-src Cloudflare Worker or ghproxy-style service).
/// Only transforms URLs from known GitHub domains when GitHub acceleration is enabled.
/// </summary>
/// <param name="url">The original download URL</param>
/// <returns>The accelerated URL if applicable, or the original URL otherwise</returns>
public static Uri? AccelerateDownloadUrl(Uri? url)
{
if (url is null)
return null;

if (!Settings.Get(Settings.K.EnableGitHubAcceleration))
return url;

string acceleratorBase = Settings.GetValue(Settings.K.GitHubAcceleratorUrl);
if (string.IsNullOrWhiteSpace(acceleratorBase))
return url;

string original = url.ToString();

// Map GitHub domains to xget-src platform keys.
// For github.com, the key is "gh". For subdomains like raw.githubusercontent.com,
// the key is the full hostname (as used by xget-src and similar proxies).
var domainMappings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase)
{
["https://github.com/"] = "gh",
["https://raw.githubusercontent.com/"] = "raw.githubusercontent.com",
["https://objects.githubusercontent.com/"] = "objects.githubusercontent.com",
["https://release-assets.githubusercontent.com/"] = "release-assets.githubusercontent.com",
};

foreach (var kvp in domainMappings)
{
if (original.StartsWith(kvp.Key, StringComparison.OrdinalIgnoreCase))
{
string path = original[kvp.Key.Length..];
var accelerated = new Uri($"{acceleratorBase.TrimEnd('/')}/{kvp.Value}/{path.TrimStart('/')}");
Logger.Debug($"GitHub acceleration: {original} -> {accelerated}");
return accelerated;
}
}

return url;
}

/// <summary>
/// Translate a string to the current language
/// </summary>
Expand Down Expand Up @@ -328,6 +374,8 @@ public static long GetFileSizeAsLong(Uri? url)
if (url is null)
return 0;

url = AccelerateDownloadUrl(url) ?? url;

try
{
using HttpClient client = new(CoreTools.GenericHttpClientParameters);
Expand All @@ -347,6 +395,8 @@ public static long GetFileSizeAsLong(Uri? url)

public static string GetFileName(Uri url)
{
url = AccelerateDownloadUrl(url) ?? url;

try
{
var handler = CoreTools.GenericHttpClientParameters;
Expand Down
15 changes: 14 additions & 1 deletion src/UniGetUI.PackageEngine.Enums/OverridenInstallationOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ public struct OverridenInstallationOptions
public bool? WinGet_SpecifyVersion = null;
public bool Pip_BreakSystemPackages = false;

/// <summary>
/// Path to a pre-downloaded installer (used by WinGet GitHub acceleration).
/// When set, PackageOperation.PrepareProcessStartInfo runs this local installer
/// instead of calling winget.exe.
/// </summary>
public string? AcceleratedInstallerPath = null;

/// <summary>
/// Installer type (msi, exe, inno, nullsoft, wix, msix, portable, burn).
/// Determines silent-install arguments for the accelerated local installer.
/// </summary>
public string? AcceleratedInstallerType = null;

public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrator = null)
{
Scope = scope;
Expand All @@ -16,6 +29,6 @@ public OverridenInstallationOptions(string? scope = null, bool? runAsAdministrat

public override string ToString()
{
return $"<Scope={Scope};RunAsAdministrator={RunAsAdministrator};WG_SpecifyVersion={WinGet_SpecifyVersion};PS_NoScope={PowerShell_DoNotSetScopeParameter};Pip_BreakSystemPackages={Pip_BreakSystemPackages}>";
return $"<Scope={Scope};RunAsAdministrator={RunAsAdministrator};WG_SpecifyVersion={WinGet_SpecifyVersion};PS_NoScope={PowerShell_DoNotSetScopeParameter};Pip_BreakSystemPackages={Pip_BreakSystemPackages};Accelerated={AcceleratedInstallerPath is not null}>";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,54 @@ public static string GetIdNamePiece(IPackage package)
public WinGetPkgOperationHelper(WinGet manager)
: base(manager) { }

/// <summary>
/// Checks whether GitHub acceleration is applicable for this package.
/// Returns true if the installer URL is from a GitHub domain and
/// acceleration is enabled (the actual download is done by
/// XGetInstallerDownloadOperation as a pre-operation).
/// </summary>
private static bool IsAccelerationApplicable(IPackage package, OperationType operation)
{
if (operation is not OperationType.Update)
return false;

if (!Settings.Get(Settings.K.EnableGitHubAcceleration))
return false;

string? acceleratorBase = Settings.GetValue(Settings.K.GitHubAcceleratorUrl);
if (string.IsNullOrWhiteSpace(acceleratorBase))
return false;

try
{
package.Details.Load().GetAwaiter().GetResult();
}
catch
{
Logger.Warn($"GitHub acceleration: failed to load details for {package.Id}");
return false;
}

Uri? installerUrl = package.Details.InstallerUrl;
if (installerUrl is null)
return false;

string urlStr = installerUrl.ToString();
bool isGitHub = urlStr.StartsWith("https://github.com/", StringComparison.OrdinalIgnoreCase)
|| urlStr.StartsWith("https://raw.githubusercontent.com/", StringComparison.OrdinalIgnoreCase)
|| urlStr.StartsWith("https://objects.githubusercontent.com/", StringComparison.OrdinalIgnoreCase)
|| urlStr.StartsWith("https://release-assets.githubusercontent.com/", StringComparison.OrdinalIgnoreCase);

if (!isGitHub)
return false;

Uri? accelerated = CoreTools.AccelerateDownloadUrl(installerUrl);
if (accelerated is null || accelerated == installerUrl)
return false;

return true;
}

protected override IReadOnlyList<string> _getOperationParameters(
IPackage package,
InstallOptions options,
Expand All @@ -41,6 +89,12 @@ OperationType operation
bool usePinget =
((WinGet)Manager).SelectedCliToolKind == WinGetCliToolKind.BundledPinget;

// GitHub acceleration: signal that local installer should be used
if (IsAccelerationApplicable(package, operation))
{
return ["--accelerated-local-install"];
}

List<string> parameters =
[
operation switch
Expand Down Expand Up @@ -242,6 +296,17 @@ protected override OperationVeredict _getOperationResult(
int returnCode
)
{
// Local accelerated installer: simple 0=success, non-zero=failure
if (package.OverridenOptions.AcceleratedInstallerPath is not null)
{
if (returnCode == 0)
{
MarkUpgradeAsDone(package);
return OperationVeredict.Success;
}
return OperationVeredict.Failure;
}

// See https://github.com/microsoft/winget-cli/blob/master/doc/windows/package-manager/winget/returnCodes.md for reference
uint uintCode = (uint)returnCode;

Expand Down
16 changes: 14 additions & 2 deletions src/UniGetUI.PackageEngine.Operations/DownloadOperation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,22 @@ protected override async Task<OperationVeredict> PerformOperation()
downloadLocation = Path.Join(downloadLocation, fileName);
}

Line($"Download URL found at {downloadUrl} ", LineType.Information);
// Apply GitHub URL acceleration if configured
Uri acceleratedUrl = CoreTools.AccelerateDownloadUrl(downloadUrl)
?? downloadUrl
?? throw new InvalidOperationException("Download URL was null after acceleration fallback");
if (acceleratedUrl != downloadUrl!)
{
Line($"Download URL accelerated to {acceleratedUrl} ", LineType.Information);
}
else
{
Line($"Download URL found at {downloadUrl} ", LineType.Information);
}

using var httpClient = new HttpClient(CoreTools.GenericHttpClientParameters);
using var response = await httpClient.GetAsync(
downloadUrl,
acceleratedUrl,
HttpCompletionOption.ResponseHeadersRead
);

Expand Down
Loading