Harden ProxiFyre installer downloads
Some checks failed
CI / Windows baseline (push) Has been cancelled

This commit is contained in:
2026-07-09 14:20:35 +03:00
parent 1bb795a532
commit 9fd0a8c0b9
7 changed files with 160 additions and 13 deletions

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "proxywarden",
"version": "1.0.1",
"version": "1.0.2",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "proxywarden",
"version": "1.0.1",
"version": "1.0.2",
"dependencies": {
"@fontsource-variable/jetbrains-mono": "^5.2.8",
"@tauri-apps/api": "^2.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "proxywarden",
"version": "1.0.1",
"version": "1.0.2",
"private": true,
"type": "module",
"description": "Standalone Windows desktop proxy management app for ProxyWarden.",

2
src-tauri/Cargo.lock generated
View File

@@ -2314,7 +2314,7 @@ dependencies = [
[[package]]
name = "proxywarden"
version = "1.0.1"
version = "1.0.2"
dependencies = [
"base64 0.22.1",
"percent-encoding",

View File

@@ -1,6 +1,6 @@
[package]
name = "proxywarden"
version = "1.0.1"
version = "1.0.2"
description = "Standalone Windows desktop proxy management app for ProxyWarden."
authors = ["ProxyWarden"]
edition = "2021"

View File

@@ -3607,8 +3607,126 @@ pub fn install_proxifyre_script(generated_config_path: &Path) -> String {
return 'x86'
}
function Invoke-Download([string]$uri, [string]$path) {
Invoke-WebRequest -UseBasicParsing -Uri $uri -OutFile $path -Headers @{ 'User-Agent' = 'proxywarden' }
function Get-SafeUriForLog([string]$uri) {
try {
$parsed = [Uri]$uri
$port = if ($parsed.IsDefaultPort) { '' } else { ":$($parsed.Port)" }
return "$($parsed.Scheme)://$($parsed.Host)$port$($parsed.AbsolutePath)"
} catch {
return '<invalid-url>'
}
}
function Invoke-ReleaseApi([string]$uri, [string]$label) {
$safeUri = Get-SafeUriForLog $uri
$headers = @{ 'User-Agent' = 'proxywarden'; 'Accept' = 'application/vnd.github+json' }
$lastError = $null
foreach ($attempt in 1..3) {
try {
return Invoke-RestMethod -Uri $uri -Headers $headers -TimeoutSec 60 -MaximumRedirection 10
} catch {
$lastError = $_.Exception.Message
if ($attempt -lt 3) {
Start-Sleep -Seconds ([Math]::Min(10, $attempt * 2))
}
}
}
throw "Не удалось получить metadata для $label ($safeUri): $lastError"
}
function Complete-Download([string]$partialPath, [string]$path, [string]$label) {
if (-not (Test-Path -LiteralPath $partialPath)) {
throw "${label}: файл не был создан."
}
$item = Get-Item -LiteralPath $partialPath
if ($item.Length -le 0) {
throw "${label}: скачанный файл пустой."
}
Move-Item -LiteralPath $partialPath -Destination $path -Force
}
function Invoke-WebClientDownload([string]$uri, [string]$partialPath) {
$client = New-Object System.Net.WebClient
try {
$client.Headers.Add('User-Agent', 'proxywarden')
$client.Headers.Add('Accept', 'application/octet-stream,*/*')
$client.DownloadFile($uri, $partialPath)
} finally {
$client.Dispose()
}
}
function Invoke-CurlDownload([string]$uri, [string]$partialPath) {
$curl = Get-Command 'curl.exe' -ErrorAction SilentlyContinue
if ($null -eq $curl) {
throw 'curl.exe не найден.'
}
$curlOutput = & $curl.Source --silent --show-error --fail --location --retry 2 --retry-delay 2 --connect-timeout 30 --max-time 180 --user-agent 'proxywarden' --output $partialPath --url $uri 2>&1
if ($LASTEXITCODE -ne 0) {
$curlMessage = ($curlOutput | Out-String).Trim()
if ([string]::IsNullOrWhiteSpace($curlMessage)) {
throw "curl.exe завершился с кодом $LASTEXITCODE."
}
throw "curl.exe завершился с кодом ${LASTEXITCODE}: $curlMessage"
}
}
function Invoke-Download([string]$uri, [string]$path, [string]$label) {
$safeUri = Get-SafeUriForLog $uri
$partialPath = "$path.part"
$headers = @{ 'User-Agent' = 'proxywarden'; 'Accept' = 'application/octet-stream,*/*' }
$webRequestError = $null
$webClientError = $null
$curlError = $null
foreach ($attempt in 1..3) {
Remove-Item -LiteralPath $partialPath -Force -ErrorAction SilentlyContinue
try {
Invoke-WebRequest -UseBasicParsing -Uri $uri -OutFile $partialPath -Headers $headers -TimeoutSec 180 -MaximumRedirection 10
Complete-Download $partialPath $path $label
return
} catch {
$webRequestError = $_.Exception.Message
Remove-Item -LiteralPath $partialPath -Force -ErrorAction SilentlyContinue
if ($attempt -lt 3) {
Start-Sleep -Seconds ([Math]::Min(10, $attempt * 2))
}
}
}
try {
Remove-Item -LiteralPath $partialPath -Force -ErrorAction SilentlyContinue
Invoke-WebClientDownload $uri $partialPath
Complete-Download $partialPath $path $label
return
} catch {
$webClientError = $_.Exception.Message
Remove-Item -LiteralPath $partialPath -Force -ErrorAction SilentlyContinue
}
try {
Remove-Item -LiteralPath $partialPath -Force -ErrorAction SilentlyContinue
Invoke-CurlDownload $uri $partialPath
Complete-Download $partialPath $path $label
return
} catch {
$curlError = $_.Exception.Message
Remove-Item -LiteralPath $partialPath -Force -ErrorAction SilentlyContinue
}
$errors = @()
if (-not [string]::IsNullOrWhiteSpace($webRequestError)) { $errors += "Invoke-WebRequest: $webRequestError" }
if (-not [string]::IsNullOrWhiteSpace($webClientError)) { $errors += "WebClient: $webClientError" }
if (-not [string]::IsNullOrWhiteSpace($curlError)) { $errors += "curl.exe: $curlError" }
$details = if ($errors.Count -gt 0) { $errors -join ' | ' } else { 'неизвестная ошибка' }
throw "Не удалось скачать $label ($safeUri): $details"
}
function Select-Asset($assets, [string]$pattern, [string]$label) {
@@ -3672,7 +3790,7 @@ pub fn install_proxifyre_script(generated_config_path: &Path) -> String {
if (-not (Test-VcRuntime $arch)) {
$vcRedistPath = Join-Path $workDir 'vc_redist.exe'
$vcRedistUrl = if ($arch -eq 'x86') { $vcRedistX86Url } else { $vcRedistX64Url }
Invoke-Download $vcRedistUrl $vcRedistPath
Invoke-Download $vcRedistUrl $vcRedistPath 'Microsoft Visual C++ Runtime'
$vcProcess = Start-Process -FilePath $vcRedistPath -ArgumentList @('/install', '/quiet', '/norestart') -Wait -PassThru -WindowStyle Hidden
if ($vcProcess.ExitCode -ne 0 -and $vcProcess.ExitCode -ne 3010 -and $vcProcess.ExitCode -ne 1638 -and -not (Test-VcRuntime $arch)) {
throw "Visual C++ Runtime завершился с кодом $($vcProcess.ExitCode)."
@@ -3680,12 +3798,12 @@ pub fn install_proxifyre_script(generated_config_path: &Path) -> String {
}
if (-not (Test-WindowsPacketFilter)) {
$ndisRelease = Invoke-RestMethod -Uri $ndisapiReleaseApi -Headers @{ 'User-Agent' = 'proxywarden' }
$ndisRelease = Invoke-ReleaseApi $ndisapiReleaseApi 'Windows Packet Filter'
$ndisPattern = if ($arch -eq 'ARM64') { 'ARM64\.msi$' } elseif ($arch -eq 'x86') { 'x86\.msi$' } else { 'x64\.msi$' }
$ndisAsset = Select-Asset $ndisRelease.assets $ndisPattern 'Windows Packet Filter'
$ndisPath = Join-Path $workDir $ndisAsset.name
$ndisLogPath = Join-Path $workDir 'windows-packet-filter-install.log'
Invoke-Download $ndisAsset.browser_download_url $ndisPath
Invoke-Download $ndisAsset.browser_download_url $ndisPath 'Windows Packet Filter'
Verify-AssetHash $ndisPath $ndisAsset
$ndisProcess = Start-Process -FilePath 'msiexec.exe' -ArgumentList @('/i', $ndisPath, '/qn', '/norestart', '/L*v', $ndisLogPath) -Wait -PassThru -WindowStyle Hidden
if ($ndisProcess.ExitCode -ne 0 -and $ndisProcess.ExitCode -ne 3010 -and -not (Test-WindowsPacketFilter)) {
@@ -3694,11 +3812,11 @@ pub fn install_proxifyre_script(generated_config_path: &Path) -> String {
}
}
$proxifyreRelease = Invoke-RestMethod -Uri $proxifyreReleaseApi -Headers @{ 'User-Agent' = 'proxywarden' }
$proxifyreRelease = Invoke-ReleaseApi $proxifyreReleaseApi 'ProxiFyre'
$proxifyrePattern = if ($arch -eq 'ARM64') { 'ARM64-signed\.zip$' } elseif ($arch -eq 'x86') { 'x86-signed\.zip$' } else { 'x64-signed\.zip$' }
$proxifyreAsset = Select-Asset $proxifyreRelease.assets $proxifyrePattern 'ProxiFyre'
$proxifyreZipPath = Join-Path $workDir $proxifyreAsset.name
Invoke-Download $proxifyreAsset.browser_download_url $proxifyreZipPath
Invoke-Download $proxifyreAsset.browser_download_url $proxifyreZipPath 'ProxiFyre'
Verify-AssetHash $proxifyreZipPath $proxifyreAsset
Expand-Archive -LiteralPath $proxifyreZipPath -DestinationPath $extractDir -Force

View File

@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "ProxyWarden",
"version": "1.0.1",
"version": "1.0.2",
"identifier": "ru.dokops.proxywarden.windows",
"build": {
"beforeDevCommand": "npm run dev",

View File

@@ -251,6 +251,35 @@ fn proxifyre_install_script_parses_as_powershell() {
cleanup(&root);
}
#[test]
fn proxifyre_install_script_uses_resilient_download_helpers() {
let root = test_root("proxifyre-install-script-downloads");
let script = commands::install_proxifyre_script(&root.join("proxifyre-app-config.json"));
assert!(script.contains("function Get-SafeUriForLog([string]$uri)"));
assert!(script.contains("function Invoke-ReleaseApi([string]$uri, [string]$label)"));
assert!(
script.contains("function Invoke-Download([string]$uri, [string]$path, [string]$label)")
);
assert!(script.contains("foreach ($attempt in 1..3)"));
assert!(script.contains("Invoke-WebClientDownload $uri $partialPath"));
assert!(script.contains("Invoke-CurlDownload $uri $partialPath"));
assert!(script.contains("--user-agent 'proxywarden' --output $partialPath --url $uri"));
assert!(script.contains("Move-Item -LiteralPath $partialPath -Destination $path -Force"));
assert!(script
.contains("Invoke-Download $vcRedistUrl $vcRedistPath 'Microsoft Visual C++ Runtime'"));
assert!(script.contains("Invoke-ReleaseApi $ndisapiReleaseApi 'Windows Packet Filter'"));
assert!(script.contains(
"Invoke-Download $ndisAsset.browser_download_url $ndisPath 'Windows Packet Filter'"
));
assert!(script.contains("Invoke-ReleaseApi $proxifyreReleaseApi 'ProxiFyre'"));
assert!(script.contains(
"Invoke-Download $proxifyreAsset.browser_download_url $proxifyreZipPath 'ProxiFyre'"
));
cleanup(&root);
}
#[test]
fn singbox_runner_preserves_installer_args_with_spaces() {
let script = commands::singbox_installer_runner_script(