Rebuild vpn proxy around gateway mode
This commit is contained in:
@@ -1,118 +0,0 @@
|
||||
# ==========================================
|
||||
# 🛠️ COMMON UTILS
|
||||
# ==========================================
|
||||
|
||||
# --- ГЛОБАЛЬНЫЕ НАСТРОЙКИ ---
|
||||
|
||||
# Режим отладки (передаётся через -Debug)
|
||||
if (-not (Test-Path variable:script:DebugMode)) {
|
||||
$script:DebugMode = $false
|
||||
}
|
||||
|
||||
function Set-DebugMode {
|
||||
param([bool]$Enabled)
|
||||
$script:DebugMode = $Enabled
|
||||
if ($Enabled) {
|
||||
Write-Host " 🔧 Debug режим включён" -ForegroundColor Magenta
|
||||
}
|
||||
}
|
||||
|
||||
function Get-DebugMode {
|
||||
return $script:DebugMode
|
||||
}
|
||||
|
||||
# --- ЦВЕТА И ВЫВОД ---
|
||||
|
||||
function Write-Step { param($msg) Write-Host "`n📦 $msg" -ForegroundColor Cyan }
|
||||
function Write-Success { param($msg) Write-Host " ✅ $msg" -ForegroundColor Green }
|
||||
function Write-Warning { param($msg) Write-Host " ⚠️ $msg" -ForegroundColor Yellow }
|
||||
function Write-Error { param($msg) Write-Host " ❌ $msg" -ForegroundColor Red }
|
||||
function Write-Info { param($msg) Write-Host " ℹ️ $msg" -ForegroundColor Gray }
|
||||
|
||||
function Write-DebugLog {
|
||||
param($msg)
|
||||
if ($script:DebugMode) {
|
||||
Write-Host " [DEBUG] $msg" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
function Write-Header {
|
||||
param($Title, [switch]$ClearScreen)
|
||||
|
||||
if ($ClearScreen -and -not $script:DebugMode) {
|
||||
Clear-Host
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host " $Title" -ForegroundColor Cyan
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# --- ЗАПУСК КОМАНД ---
|
||||
|
||||
function Invoke-Silent {
|
||||
param(
|
||||
[string]$FilePath,
|
||||
[string]$Arguments,
|
||||
[switch]$Wait
|
||||
)
|
||||
|
||||
$psi = New-Object System.Diagnostics.ProcessStartInfo
|
||||
$psi.FileName = $FilePath
|
||||
$psi.Arguments = $Arguments
|
||||
$psi.UseShellExecute = $false
|
||||
$psi.CreateNoWindow = $true
|
||||
|
||||
if (-not $script:DebugMode) {
|
||||
$psi.RedirectStandardOutput = $true
|
||||
$psi.RedirectStandardError = $true
|
||||
}
|
||||
|
||||
$process = [System.Diagnostics.Process]::Start($psi)
|
||||
|
||||
if ($Wait) {
|
||||
$process.WaitForExit()
|
||||
return $process.ExitCode
|
||||
}
|
||||
|
||||
return $process
|
||||
}
|
||||
|
||||
# --- ПОЛЕЗНЫЕ ФУНКЦИИ ---
|
||||
|
||||
function Get-ScriptDirectory {
|
||||
if ($PSScriptRoot) { return $PSScriptRoot }
|
||||
return Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
}
|
||||
|
||||
function Ensure-Admin {
|
||||
$isAdmin = ([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole] "Administrator")
|
||||
if (-not $isAdmin) {
|
||||
Write-Host "⛔ Требуются права АДМИНИСТРАТОРА!" -ForegroundColor Red
|
||||
Write-Host " Пожалуйста, запустите скрипт от имени администратора." -ForegroundColor Gray
|
||||
Start-Sleep -Seconds 3
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
function Show-Menu {
|
||||
param(
|
||||
[string]$Title,
|
||||
[System.Collections.Specialized.OrderedDictionary]$Options,
|
||||
[string]$Prompt = "👉 Ваш выбор"
|
||||
)
|
||||
|
||||
if ($Title) {
|
||||
Write-Host "`n$Title" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
$keys = $Options.Keys
|
||||
foreach ($key in $keys) {
|
||||
Write-Host " [$key] $($Options[$key])" -ForegroundColor White
|
||||
}
|
||||
Write-Host ""
|
||||
|
||||
return Read-Host "$Prompt"
|
||||
}
|
||||
@@ -1,144 +0,0 @@
|
||||
# ==========================================
|
||||
# 🌐 NET UTILS
|
||||
# ==========================================
|
||||
|
||||
# --- CONFIG ---
|
||||
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
||||
|
||||
# --- ФУНКЦИИ ---
|
||||
|
||||
$script:HwidFile = "C:\Tools\sing-box\hwid"
|
||||
$script:AppName = "VPN-Proxy-Control by Dokril"
|
||||
|
||||
function Get-HWID {
|
||||
# Генерация или чтение HWID из файла
|
||||
if (Test-Path $script:HwidFile) {
|
||||
return (Get-Content $script:HwidFile -Raw).Trim()
|
||||
}
|
||||
|
||||
# Генерируем новый HWID
|
||||
$hwid = [Guid]::NewGuid().ToString("N").Substring(0, 16)
|
||||
|
||||
# Сохраняем
|
||||
$dir = Split-Path $script:HwidFile -Parent
|
||||
if (!(Test-Path $dir)) { New-Item -ItemType Directory -Path $dir -Force | Out-Null }
|
||||
Set-Content -Path $script:HwidFile -Value $hwid
|
||||
|
||||
return $hwid
|
||||
}
|
||||
|
||||
function Get-SubscriptionHeaders {
|
||||
# Формируем заголовки как в server.py
|
||||
$osName = "windows"
|
||||
$osVersion = [Environment]::OSVersion.Version.ToString()
|
||||
|
||||
return @{
|
||||
"User-Agent" = "singbox"
|
||||
"x-hwid" = (Get-HWID)
|
||||
"x-device-os" = $osName
|
||||
"x-ver-os" = $osVersion
|
||||
"x-device-model" = $script:AppName
|
||||
}
|
||||
}
|
||||
|
||||
function Download-File {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$Destination,
|
||||
[string]$UserAgent = "VPN-Proxy-Installer"
|
||||
)
|
||||
|
||||
try {
|
||||
$req = [System.Net.HttpWebRequest]::Create($Url)
|
||||
$req.UserAgent = $UserAgent
|
||||
$resp = $req.GetResponse()
|
||||
|
||||
$stream = $resp.GetResponseStream()
|
||||
$fs = [System.IO.File]::Create($Destination)
|
||||
$msgLen = $resp.ContentLength
|
||||
|
||||
$buffer = New-Object byte[] 10240
|
||||
$count = 0
|
||||
$total = 0
|
||||
|
||||
do {
|
||||
$count = $stream.Read($buffer, 0, $buffer.Length)
|
||||
$fs.Write($buffer, 0, $count)
|
||||
$total += $count
|
||||
# Можно добавить прогресс бар, но пока просто качаем
|
||||
} while ($count -gt 0)
|
||||
|
||||
$fs.Close()
|
||||
$stream.Close()
|
||||
$resp.Close()
|
||||
|
||||
# Unblock file to prevent "Mark of the Web" issues
|
||||
Unblock-File -Path $Destination -ErrorAction SilentlyContinue
|
||||
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Error "Ошибка скачивания: $_"
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function Get-SubscriptionData {
|
||||
param(
|
||||
[string]$Url,
|
||||
[string]$UserAgent = "singbox",
|
||||
$Headers = @{}
|
||||
)
|
||||
|
||||
Write-Info "Загружаю подписку..."
|
||||
|
||||
$rawContent = $null
|
||||
$userInfo = @{}
|
||||
|
||||
# 1. Получаем ответ
|
||||
try {
|
||||
$response = Invoke-WebRequest -Uri $Url -Headers $Headers -TimeoutSec 15 -UseBasicParsing
|
||||
$rawContent = $response.Content
|
||||
|
||||
# Парсим subscription-userinfo header
|
||||
$userInfoHeader = $response.Headers["subscription-userinfo"]
|
||||
if ($userInfoHeader) {
|
||||
$parts = $userInfoHeader -split ";"
|
||||
foreach ($part in $parts) {
|
||||
if ($part -match "(\w+)=(\d+)") {
|
||||
$userInfo[$matches[1]] = [int64]$matches[2]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
return @{
|
||||
success = $false
|
||||
error = "Ошибка загрузки: $($_.Exception.Message)"
|
||||
rawContent = $null
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Пробуем парсить как JSON
|
||||
try {
|
||||
$config = $rawContent | ConvertFrom-Json
|
||||
return @{
|
||||
success = $true
|
||||
config = $config
|
||||
rawContent = $rawContent
|
||||
userInfo = $userInfo
|
||||
}
|
||||
}
|
||||
catch {
|
||||
# JSON не распарсился — возвращаем rawContent для дальнейшей обработки
|
||||
return @{
|
||||
success = $false
|
||||
error = "Ответ не в формате JSON (возможно Base64 или список ссылок)"
|
||||
rawContent = $rawContent
|
||||
userInfo = $userInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,175 +0,0 @@
|
||||
# ==========================================
|
||||
# 🖥️ SYSTEM UTILS
|
||||
# ==========================================
|
||||
|
||||
|
||||
# --- СИСТЕМНАЯ ИНФОРМАЦИЯ ---
|
||||
|
||||
function Get-SystemInfo {
|
||||
return @{
|
||||
os = "windows"
|
||||
version = [System.Environment]::OSVersion.Version.Major.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-VCRedist {
|
||||
Write-Info "Проверка Visual C++ Redistributable..."
|
||||
|
||||
# Check registry for VC++ 2015-2022 (x64)
|
||||
# Key: HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64
|
||||
$regPath = "HKLM:\SOFTWARE\Microsoft\VisualStudio\14.0\VC\Runtimes\x64"
|
||||
if (Test-Path $regPath) {
|
||||
$installed = (Get-ItemProperty -Path $regPath).Installed
|
||||
if ($installed -eq 1) {
|
||||
Write-Success "Visual C++ Redistributable уже установлен."
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Write-Warning "Visual C++ Redistributable не найден. Устанавливаю..."
|
||||
|
||||
$vcUrl = "https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
$vcFile = "$env:TEMP\vc_redist.x64.exe"
|
||||
|
||||
if (Download-File -Url $vcUrl -Destination $vcFile) {
|
||||
Write-Step "Установка библиотек Visual C++..."
|
||||
$process = Start-Process -FilePath $vcFile -ArgumentList "/install", "/quiet", "/norestart" -PassThru -Wait
|
||||
|
||||
if ($process.ExitCode -eq 0 -or $process.ExitCode -eq 3010) {
|
||||
# 3010 = reboot required (usually works without immediate reboot)
|
||||
Write-Success "Библиотеки установлены!"
|
||||
}
|
||||
else {
|
||||
Write-Error "Ошибка установки VC++ (Код: $($process.ExitCode))"
|
||||
Write-Host " Попробуйте установить вручную: https://aka.ms/vs/17/release/vc_redist.x64.exe"
|
||||
}
|
||||
|
||||
Remove-Item $vcFile -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
|
||||
# --- DOCKER ---
|
||||
|
||||
function Test-Docker {
|
||||
$status = @{
|
||||
Installed = $false
|
||||
Running = $false
|
||||
Compose = $false
|
||||
}
|
||||
|
||||
try {
|
||||
$ver = docker --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { $status.Installed = $true }
|
||||
}
|
||||
catch {}
|
||||
|
||||
if ($status.Installed) {
|
||||
try {
|
||||
$info = docker info 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { $status.Running = $true }
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
if ($status.Running) {
|
||||
try {
|
||||
$comp = docker compose version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { $status.Compose = $true }
|
||||
}
|
||||
catch {
|
||||
# Check legacy
|
||||
try {
|
||||
$comp = docker-compose --version 2>&1
|
||||
if ($LASTEXITCODE -eq 0) { $status.Compose = $true }
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
|
||||
return $status
|
||||
}
|
||||
|
||||
# --- СЛУЖБЫ И ЗАДАЧИ ---
|
||||
|
||||
function Manage-ScheduledTask {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$ExePath,
|
||||
[string]$Arguments,
|
||||
[string]$WorkDir,
|
||||
[string]$Action = "Install" # Install, Uninstall, Start, Stop
|
||||
)
|
||||
|
||||
switch ($Action) {
|
||||
"Install" {
|
||||
# Удаляем старую
|
||||
Unregister-ScheduledTask -TaskName $Name -Confirm:$false -ErrorAction SilentlyContinue
|
||||
|
||||
$act = New-ScheduledTaskAction -Execute "$ExePath" -Argument "$Arguments" -WorkingDirectory $WorkDir
|
||||
$trig = New-ScheduledTaskTrigger -AtStartup
|
||||
$princ = New-ScheduledTaskPrincipal -UserId "SYSTEM" -LogonType ServiceAccount -RunLevel Highest
|
||||
$sett = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
|
||||
|
||||
Register-ScheduledTask -TaskName $Name -Action $act -Trigger $trig -Principal $princ -Settings $sett -Force | Out-Null
|
||||
return $true
|
||||
}
|
||||
"Uninstall" {
|
||||
Unregister-ScheduledTask -TaskName $Name -Confirm:$false -ErrorAction SilentlyContinue
|
||||
}
|
||||
"Start" {
|
||||
Start-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue
|
||||
}
|
||||
"Stop" {
|
||||
Stop-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue
|
||||
# Пытаемся убить процесс по имени exe
|
||||
if ($ExePath) {
|
||||
$procName = [System.IO.Path]::GetFileNameWithoutExtension($ExePath)
|
||||
if ($procName) {
|
||||
Stop-Process -Name $procName -Force -ErrorAction SilentlyContinue
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Get-TaskStatus {
|
||||
param([string]$Name)
|
||||
$task = Get-ScheduledTask -TaskName $Name -ErrorAction SilentlyContinue
|
||||
if ($task) {
|
||||
# Если задача в статусе Running — возвращаем Running
|
||||
if ($task.State -eq "Running") {
|
||||
return "Running"
|
||||
}
|
||||
|
||||
# Если задача Ready — проверяем, работает ли процесс sing-box
|
||||
# (scheduled task может быть Ready даже когда процесс работает)
|
||||
$process = Get-Process -Name "sing-box" -ErrorAction SilentlyContinue
|
||||
if ($process) {
|
||||
return "Running"
|
||||
}
|
||||
|
||||
return $task.State
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
|
||||
|
||||
function Ensure-FirewallPort {
|
||||
param(
|
||||
[int]$Port,
|
||||
[string]$Name,
|
||||
[string]$Protocol = "TCP"
|
||||
)
|
||||
|
||||
$rule = Get-NetFirewallRule -DisplayName $Name -ErrorAction SilentlyContinue
|
||||
if (-not $rule) {
|
||||
New-NetFirewallRule -DisplayName $Name -Direction Inbound -LocalPort $Port -Protocol $Protocol -Action Allow -Profile Any | Out-Null
|
||||
return $true
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Get-LocalIPs {
|
||||
return (Get-NetIPAddress -AddressFamily IPv4 -InterfaceAlias * | Where-Object { $_.IPAddress -notmatch "^127\." -and $_.IPAddress -notmatch "^169\.254\." }).IPAddress
|
||||
}
|
||||
@@ -1,420 +0,0 @@
|
||||
# ==========================================
|
||||
# 🎮 DISCORD PROXY SETUP
|
||||
# ==========================================
|
||||
|
||||
param(
|
||||
[switch]$Force,
|
||||
[switch]$Debug
|
||||
)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$ScriptDir\lib\Common.ps1"
|
||||
. "$ScriptDir\lib\Net.ps1"
|
||||
. "$ScriptDir\lib\System.ps1"
|
||||
|
||||
if ($Debug) { Set-DebugMode -Enabled $true }
|
||||
|
||||
Write-Header "НАСТРОЙКА DISCORD / VESKTOP" -ClearScreen
|
||||
|
||||
Ensure-Admin
|
||||
|
||||
$InstallPath = "C:\Tools\ProxiFyre"
|
||||
$ConfigPath = "$InstallPath\app-config.json"
|
||||
$DriverUrl = "https://github.com/wiresock/ndisapi/releases/download/v3.6.2/Windows.Packet.Filter.3.6.2.1.x64.msi"
|
||||
$AppUrl = "https://github.com/wiresock/proxifyre/releases/download/v2.1.4/ProxiFyre-v2.1.4-x64-signed.zip"
|
||||
|
||||
# --- ФУНКЦИИ ---
|
||||
|
||||
function Test-ProxyConnection {
|
||||
param([string]$ProxyAddr)
|
||||
|
||||
Write-Info "Проверка подключения к прокси $ProxyAddr..."
|
||||
|
||||
try {
|
||||
$parts = $ProxyAddr -split ":"
|
||||
$host_ = $parts[0]
|
||||
$port = [int]$parts[1]
|
||||
|
||||
# 1. Проверяем TCP соединение
|
||||
$tcp = New-Object System.Net.Sockets.TcpClient
|
||||
$tcp.Connect($host_, $port)
|
||||
$tcp.Close()
|
||||
Write-Success "TCP соединение установлено"
|
||||
|
||||
# 2. Пробуем получить внешний IP через прокси (используем curl для SOCKS5)
|
||||
try {
|
||||
$result = & curl.exe -s -x "socks5://$ProxyAddr" "http://v4.ident.me" --connect-timeout 5 2>$null
|
||||
if ($result -match "^\d+\.\d+\.\d+\.\d+$") {
|
||||
Write-Success "Внешний IP через прокси: $result"
|
||||
return $true
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
|
||||
Write-Warning "TCP работает, но не удалось получить IP. Возможно прокси не полностью настроен."
|
||||
return $true
|
||||
}
|
||||
catch {
|
||||
Write-Error "Не удалось подключиться к $ProxyAddr"
|
||||
Write-Host " Убедитесь, что прокси запущен и доступен." -ForegroundColor Gray
|
||||
return $false
|
||||
}
|
||||
}
|
||||
|
||||
function Get-CurrentConfig {
|
||||
if (Test-Path $ConfigPath) {
|
||||
try {
|
||||
$cfg = Get-Content $ConfigPath -Raw | ConvertFrom-Json
|
||||
return @{
|
||||
Apps = $cfg.proxies[0].appNames -join ", "
|
||||
Proxy = $cfg.proxies[0].socks5ProxyEndpoint
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
return $null
|
||||
}
|
||||
|
||||
function Install-ProxiFyre {
|
||||
# 0. Остановка старых процессов (чтобы файлы не были заблокированы)
|
||||
Write-Step "Остановка старых процессов..."
|
||||
Stop-Service "ProxiFyreService" -Force -ErrorAction SilentlyContinue
|
||||
Stop-Process -Name "ProxiFyre" -Force -ErrorAction SilentlyContinue
|
||||
Start-Sleep -Seconds 2
|
||||
|
||||
# Установка драйвера
|
||||
Write-Step "Установка драйвера..."
|
||||
$msi = "$env:TEMP\WinpkFilter.msi"
|
||||
if (Download-File -Url $DriverUrl -Destination $msi) {
|
||||
Start-Process msiexec.exe -ArgumentList "/i `"$msi`" /qn /norestart" -Wait
|
||||
Write-Success "Драйвер готов"
|
||||
}
|
||||
|
||||
# Установка ProxiFyre
|
||||
Write-Step "Установка ProxiFyre..."
|
||||
New-Item -ItemType Directory -Path $InstallPath -Force | Out-Null
|
||||
$zip = "$env:TEMP\ProxiFyre.zip"
|
||||
if (Download-File -Url $AppUrl -Destination $zip) {
|
||||
Expand-Archive -Path $zip -DestinationPath $InstallPath -Force
|
||||
$exe = Get-ChildItem $InstallPath -Recurse -Filter "ProxiFyre.exe" | Select-Object -First 1
|
||||
if ($exe.DirectoryName -ne $InstallPath) {
|
||||
Copy-Item "$($exe.DirectoryName)\*" $InstallPath -Recurse -Force
|
||||
}
|
||||
Write-Success "Распаковано"
|
||||
}
|
||||
|
||||
# Создание правил Firewall
|
||||
Write-Step "Настройка Windows Firewall..."
|
||||
$exePath = "$InstallPath\ProxiFyre.exe"
|
||||
$ruleName = "ProxiFyre"
|
||||
|
||||
# Удаляем старые правила
|
||||
Remove-NetFirewallRule -DisplayName "$ruleName*" -ErrorAction SilentlyContinue
|
||||
|
||||
# Входящее правило
|
||||
New-NetFirewallRule -DisplayName "$ruleName (Inbound)" `
|
||||
-Direction Inbound `
|
||||
-Action Allow `
|
||||
-Program $exePath `
|
||||
-Profile Domain, Private, Public `
|
||||
-Description "Разрешить входящие соединения для ProxiFyre" | Out-Null
|
||||
|
||||
# Исходящее правило
|
||||
New-NetFirewallRule -DisplayName "$ruleName (Outbound)" `
|
||||
-Direction Outbound `
|
||||
-Action Allow `
|
||||
-Program $exePath `
|
||||
-Profile Domain, Private, Public `
|
||||
-Description "Разрешить исходящие соединения для ProxiFyre" | Out-Null
|
||||
|
||||
Write-Success "Правила Firewall созданы"
|
||||
}
|
||||
|
||||
function Configure-And-Start {
|
||||
param($TargetApps, $ProxyAddr)
|
||||
|
||||
# Конфиг (гарантируем, что appNames - массив)
|
||||
$appNamesArray = @($TargetApps)
|
||||
$cfg = @{
|
||||
logLevel = "Info"
|
||||
proxies = @(@{
|
||||
appNames = $appNamesArray
|
||||
socks5ProxyEndpoint = $ProxyAddr
|
||||
supportedProtocols = @("TCP", "UDP")
|
||||
})
|
||||
excludes = @()
|
||||
}
|
||||
$cfg | ConvertTo-Json -Depth 5 | Set-Content $ConfigPath -Encoding UTF8
|
||||
|
||||
# Служба
|
||||
Write-Step "Перезапуск службы..."
|
||||
if (Get-DebugMode) {
|
||||
& "$InstallPath\ProxiFyre.exe" stop
|
||||
& "$InstallPath\ProxiFyre.exe" install
|
||||
& "$InstallPath\ProxiFyre.exe" start
|
||||
}
|
||||
else {
|
||||
& "$InstallPath\ProxiFyre.exe" stop 2>&1 | Out-Null
|
||||
& "$InstallPath\ProxiFyre.exe" install 2>&1 | Out-Null
|
||||
& "$InstallPath\ProxiFyre.exe" start 2>&1 | Out-Null
|
||||
}
|
||||
|
||||
# Мониторинг запуска (10 сек)
|
||||
Write-Info "Проверка стабильности запуска (10 сек)..."
|
||||
$lastLogSize = 0
|
||||
$logFile = $null
|
||||
|
||||
for ($i = 1; $i -le 10; $i++) {
|
||||
Start-Sleep -Seconds 1
|
||||
|
||||
# 1. Ищем файл логов (если еще не нашли)
|
||||
if (-not $logFile) {
|
||||
$logFile = Get-ChildItem "$InstallPath\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1
|
||||
}
|
||||
|
||||
# 2. Выводим новые строки лога
|
||||
if ($logFile) {
|
||||
try {
|
||||
$stream = [System.IO.File]::Open($logFile.FullName, 'Open', 'Read', 'ReadWrite')
|
||||
if ($stream.Length -gt $lastLogSize) {
|
||||
$stream.Seek($lastLogSize, 'Begin') | Out-Null
|
||||
$reader = New-Object System.IO.StreamReader($stream)
|
||||
$content = $reader.ReadToEnd()
|
||||
$newPos = $stream.Position # Сохраняем позицию
|
||||
$reader.Dispose() # Закрывает поток
|
||||
$lastLogSize = $newPos
|
||||
|
||||
if (-not [string]::IsNullOrWhiteSpace($content)) {
|
||||
$content -split "`r`n" | Where-Object { -not [string]::IsNullOrWhiteSpace($_) } | ForEach-Object {
|
||||
Write-Host " LOG: $_" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
$stream.Dispose()
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# 3. Проверяем статус службы
|
||||
$svc = Get-Service -Name "ProxiFyreService" -ErrorAction SilentlyContinue
|
||||
if ($svc.Status -ne 'Running') {
|
||||
Write-Error "Служба упала при запуске! (Код 1064 или другая ошибка)"
|
||||
Write-Host " Попробуйте запустить вручную для диагностики." -ForegroundColor Gray
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
Write-Success "Готово! Служба стабильна."
|
||||
}
|
||||
|
||||
function Get-AppPath {
|
||||
Write-Host "`n📁 Укажите путь до папки с приложением" -ForegroundColor Yellow
|
||||
Write-Host " (Будут проксированы все .exe из этой папки)" -ForegroundColor Gray
|
||||
|
||||
# Стандартные пути установки Discord-клиентов
|
||||
$defaultPaths = @{
|
||||
"Discord" = "$env:LOCALAPPDATA\Discord"
|
||||
"Discord PTB" = "$env:LOCALAPPDATA\DiscordPTB"
|
||||
"Discord Canary" = "$env:LOCALAPPDATA\DiscordCanary"
|
||||
"Vesktop" = "$env:LOCALAPPDATA\vesktop"
|
||||
"Lightcord" = "$env:LOCALAPPDATA\Lightcord"
|
||||
}
|
||||
|
||||
$suggestions = @()
|
||||
foreach ($app in $defaultPaths.Keys) {
|
||||
if (Test-Path $defaultPaths[$app]) {
|
||||
$suggestions += @{ Name = $app; Path = $defaultPaths[$app] }
|
||||
}
|
||||
}
|
||||
|
||||
if ($suggestions.Count -gt 0) {
|
||||
Write-Host "`n Найденные приложения:" -ForegroundColor Cyan
|
||||
for ($i = 0; $i -lt $suggestions.Count; $i++) {
|
||||
Write-Host " [$($i+1)] $($suggestions[$i].Name): $($suggestions[$i].Path)" -ForegroundColor Gray
|
||||
}
|
||||
Write-Host " [c] Указать свой путь" -ForegroundColor Gray
|
||||
|
||||
$choice = Read-Host "`n Выберите"
|
||||
if ($choice -match "^\d+$" -and [int]$choice -ge 1 -and [int]$choice -le $suggestions.Count) {
|
||||
return @($suggestions[[int]$choice - 1].Path)
|
||||
}
|
||||
}
|
||||
|
||||
# Ручной ввод пути
|
||||
while ($true) {
|
||||
$path = Read-Host " Путь до папки"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($path)) {
|
||||
Write-Warning "Путь не указан"
|
||||
continue
|
||||
}
|
||||
|
||||
if (Test-Path $path) {
|
||||
$exeCount = (Get-ChildItem $path -Filter "*.exe" -Recurse -ErrorAction SilentlyContinue).Count
|
||||
if ($exeCount -gt 0) {
|
||||
Write-Success "Найдено $exeCount исполняемых файлов"
|
||||
return @($path)
|
||||
}
|
||||
else {
|
||||
Write-Warning "В папке не найдено .exe файлов"
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Error "Папка не существует: $path"
|
||||
}
|
||||
|
||||
$retry = Read-Host " Попробовать другой путь? (y/n)"
|
||||
if ($retry -ne 'y') { return $null }
|
||||
}
|
||||
}
|
||||
|
||||
function Select-Apps {
|
||||
Write-Host "`n🎮 Какие приложения проксировать?" -ForegroundColor Yellow
|
||||
$appOpts = [Ordered]@{
|
||||
"1" = "Discord (по имени процесса)"
|
||||
"2" = "Vesktop (по имени процесса)"
|
||||
"3" = "Discord + Vesktop (по имени процесса)"
|
||||
"4" = "Указать путь до папки приложения"
|
||||
}
|
||||
$appChoice = Show-Menu -Options $appOpts
|
||||
|
||||
# Discord запускается через Update.exe, нужно перехватывать оба процесса
|
||||
$result = switch ($appChoice) {
|
||||
"1" { @("Discord", "Update") }
|
||||
"2" { @("Vesktop") }
|
||||
"3" { @("Vesktop", "Discord", "Update") }
|
||||
"4" { Get-AppPath }
|
||||
default { @("Discord", "Update") }
|
||||
}
|
||||
return $result
|
||||
}
|
||||
|
||||
function Get-ProxyAddress {
|
||||
# Проверяем локальный sing-box
|
||||
$singboxStatus = Get-TaskStatus -Name "SingBoxProxy"
|
||||
$localProxy = "127.0.0.1:1080"
|
||||
|
||||
if ($singboxStatus -eq "Running") {
|
||||
Write-Info "Обнаружен работающий VPN клиент (Sing-box)."
|
||||
Write-Host " Рекомендуется использовать локальный прокси: " -NoNewline -ForegroundColor Gray
|
||||
Write-Host $localProxy -ForegroundColor Green
|
||||
|
||||
$useLocal = Read-Host " Использовать локальный? (y/n) [y]"
|
||||
if ($useLocal -ne 'n') {
|
||||
return $localProxy
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Warning "VPN клиент не запущен!"
|
||||
Write-Host " Вы можете указать адрес удалённого прокси." -ForegroundColor Gray
|
||||
}
|
||||
|
||||
# Запрашиваем адрес
|
||||
while ($true) {
|
||||
$proxyAddr = Read-Host "`n Введите адрес прокси (IP:порт)"
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($proxyAddr)) {
|
||||
Write-Warning "Адрес не указан"
|
||||
continue
|
||||
}
|
||||
|
||||
if ($proxyAddr -notmatch "^[\d\.]+:\d+$") {
|
||||
Write-Error "Неверный формат. Ожидается: IP:порт (например 192.168.1.100:1080)"
|
||||
continue
|
||||
}
|
||||
|
||||
# Проверяем подключение
|
||||
if (Test-ProxyConnection -ProxyAddr $proxyAddr) {
|
||||
return $proxyAddr
|
||||
}
|
||||
|
||||
$retry = Read-Host " Попробовать другой адрес? (y/n)"
|
||||
if ($retry -ne 'y') { return $null }
|
||||
}
|
||||
}
|
||||
|
||||
# --- MAIN ---
|
||||
|
||||
$isInstalled = Test-Path "$InstallPath\ProxiFyre.exe"
|
||||
$discSvc = Get-Service -Name "ProxiFyreService" -ErrorAction SilentlyContinue
|
||||
$currentConfig = Get-CurrentConfig
|
||||
|
||||
if ($isInstalled -and $currentConfig -and -not $Force) {
|
||||
# Уже установлено — показываем меню управления
|
||||
Write-Info "ProxiFyre уже установлен."
|
||||
Write-Host ""
|
||||
Write-Host " Статус: " -NoNewline -ForegroundColor Gray
|
||||
if ($discSvc.Status -eq 'Running') {
|
||||
Write-Host "АКТИВЕН" -ForegroundColor Green
|
||||
}
|
||||
else {
|
||||
Write-Host "ОСТАНОВЛЕН" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host " Приложения: $($currentConfig.Apps)" -ForegroundColor Gray
|
||||
Write-Host " Прокси: $($currentConfig.Proxy)" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
$opts = [Ordered]@{
|
||||
"1" = "Изменить настройки (приложения/прокси)"
|
||||
"2" = "Проверить подключение к прокси"
|
||||
"3" = "Перезапустить службу"
|
||||
"4" = "Остановить службу"
|
||||
"5" = "Переустановить"
|
||||
"b" = "Назад"
|
||||
}
|
||||
|
||||
$action = Show-Menu -Options $opts
|
||||
|
||||
switch ($action) {
|
||||
"1" {
|
||||
$targetApps = Select-Apps
|
||||
$proxyAddr = Get-ProxyAddress
|
||||
if ($proxyAddr) {
|
||||
Configure-And-Start -TargetApps $targetApps -ProxyAddr $proxyAddr
|
||||
}
|
||||
}
|
||||
"2" {
|
||||
Test-ProxyConnection -ProxyAddr $currentConfig.Proxy | Out-Null
|
||||
}
|
||||
"3" {
|
||||
Write-Step "Перезапуск службы..."
|
||||
Start-Process "$InstallPath\ProxiFyre.exe" -ArgumentList "stop" -Wait -NoNewWindow
|
||||
Start-Process "$InstallPath\ProxiFyre.exe" -ArgumentList "start" -Wait -NoNewWindow
|
||||
Write-Success "Перезапущено!"
|
||||
}
|
||||
"4" {
|
||||
Start-Process "$InstallPath\ProxiFyre.exe" -ArgumentList "stop" -Wait -NoNewWindow
|
||||
Write-Success "Остановлено!"
|
||||
}
|
||||
"5" {
|
||||
$Force = $true
|
||||
}
|
||||
"b" { exit }
|
||||
}
|
||||
|
||||
if (-not $Force) {
|
||||
Start-Sleep -Seconds 2
|
||||
exit
|
||||
}
|
||||
}
|
||||
|
||||
# --- НОВАЯ УСТАНОВКА ---
|
||||
|
||||
if (-not $isInstalled -or $Force) {
|
||||
Ensure-VCRedist
|
||||
Install-ProxiFyre
|
||||
}
|
||||
|
||||
$targetApps = Select-Apps
|
||||
$proxyAddr = Get-ProxyAddress
|
||||
|
||||
if (-not $proxyAddr) {
|
||||
Write-Error "Прокси не настроен. Выход."
|
||||
Start-Sleep -Seconds 2
|
||||
exit
|
||||
}
|
||||
|
||||
Configure-And-Start -TargetApps $targetApps -ProxyAddr $proxyAddr
|
||||
Start-Sleep -Seconds 3
|
||||
@@ -1,417 +0,0 @@
|
||||
# ==========================================
|
||||
# 📦 SING-BOX NATIVE INSTALLER
|
||||
# ==========================================
|
||||
|
||||
param(
|
||||
[switch]$Force,
|
||||
[switch]$Debug,
|
||||
[string]$SubscriptionUrl = ""
|
||||
)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$ScriptDir\lib\Common.ps1"
|
||||
. "$ScriptDir\lib\Net.ps1"
|
||||
. "$ScriptDir\lib\System.ps1"
|
||||
|
||||
# --- CONFIG ---
|
||||
$SingboxVersion = "1.11.4"
|
||||
$InstallDir = "C:\Tools\sing-box"
|
||||
$LocalProxyPort = 1080
|
||||
$SingboxUrl = "https://github.com/SagerNet/sing-box/releases/download/v$SingboxVersion/sing-box-$SingboxVersion-windows-amd64.zip"
|
||||
$TaskName = "SingBoxProxy"
|
||||
|
||||
Ensure-Admin
|
||||
|
||||
# --- LOGIC ---
|
||||
|
||||
function Select-Server {
|
||||
param($Config)
|
||||
|
||||
$outbounds = $Config.outbounds
|
||||
$servers = @()
|
||||
|
||||
foreach ($outbound in $outbounds) {
|
||||
if ($outbound.type -in @("vless", "vmess", "trojan", "shadowsocks", "hysteria2")) {
|
||||
$servers += @{
|
||||
tag = $outbound.tag
|
||||
type = $outbound.type
|
||||
server = $outbound.server
|
||||
server_port = $outbound.server_port
|
||||
outbound = $outbound
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($servers.Count -eq 0) {
|
||||
Write-Error "Серверы не найдены в подписке!"
|
||||
return $null
|
||||
}
|
||||
|
||||
$options = [Ordered]@{}
|
||||
for ($i = 0; $i -lt $servers.Count; $i++) {
|
||||
$s = $servers[$i]
|
||||
$options["$($i+1)"] = "$($s.tag) ($($s.server):$($s.server_port))"
|
||||
}
|
||||
|
||||
$choice = Show-Menu -Title "🌐 Доступные серверы" -Options $options -Prompt "👉 Выберите сервер (номер)"
|
||||
$index = [int]$choice - 1
|
||||
|
||||
if ($index -lt 0 -or $index -ge $servers.Count) {
|
||||
Write-Error "Неверный выбор!"
|
||||
return $null
|
||||
}
|
||||
|
||||
return $servers[$index]
|
||||
}
|
||||
|
||||
function New-SingboxConfig {
|
||||
param($Outbound, $Port)
|
||||
|
||||
return @{
|
||||
log = @{ level = "info"; timestamp = $true; output = "$InstallDir\singbox.log" }
|
||||
dns = @{ independent_cache = $true }
|
||||
inbounds = @(
|
||||
@{
|
||||
type = "socks"
|
||||
tag = "socks-in"
|
||||
listen = "0.0.0.0"
|
||||
listen_port = $Port
|
||||
}
|
||||
)
|
||||
outbounds = @(
|
||||
$Outbound,
|
||||
@{ type = "direct"; tag = "direct" }
|
||||
)
|
||||
route = @{
|
||||
final = $Outbound.tag
|
||||
auto_detect_interface = $true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Parse-VlessUrl {
|
||||
param([string]$Url)
|
||||
|
||||
if (-not $Url.StartsWith("vless://")) { throw "URL должен начинаться с vless://" }
|
||||
|
||||
# Remove scheme
|
||||
$raw = $Url.Substring(8)
|
||||
|
||||
# Split fragment
|
||||
$tag = "reality"
|
||||
if ($raw -match "#(.*)$") {
|
||||
$tag = [System.Web.HttpUtility]::UrlDecode($matches[1])
|
||||
$raw = $raw -replace "#.*$", ""
|
||||
}
|
||||
|
||||
# Split query
|
||||
$queryStr = ""
|
||||
if ($raw -match "\?(.*)$") {
|
||||
$queryStr = $matches[1]
|
||||
$raw = $raw -replace "\?.*$", ""
|
||||
}
|
||||
|
||||
# Parse UUID@HOST:PORT
|
||||
if ($raw -notmatch "([^@]+)@([^:]+):(\d+)") { throw "Неверный формат vless (ожидается uuid@host:port)" }
|
||||
$uuid = $matches[1][0]
|
||||
$serverHost = $matches[2][0]
|
||||
$port = [int]$matches[3][0] # Fix for regex object access in PS
|
||||
|
||||
if (-not $uuid) {
|
||||
# Fallback if regex returns match info differently in different PS versions
|
||||
$uuid = $matches[1]
|
||||
$serverHost = $matches[2]
|
||||
$port = [int]$matches[3]
|
||||
}
|
||||
|
||||
|
||||
# Parse Query
|
||||
$params = @{}
|
||||
if ($queryStr) {
|
||||
$parts = $queryStr -split "&"
|
||||
foreach ($p in $parts) {
|
||||
$kv = $p -split "="
|
||||
if ($kv.Count -eq 2) {
|
||||
$params[[System.Web.HttpUtility]::UrlDecode($kv[0])] = [System.Web.HttpUtility]::UrlDecode($kv[1])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Extract
|
||||
$pbk = if ($params["pbk"]) { $params["pbk"] } else { throw "Отсутствует параметр pbk (Public Key)" }
|
||||
$sid = if ($params["sid"]) { $params["sid"] } else { throw "Отсутствует параметр sid (Short ID)" }
|
||||
$sni = if ($params["sni"]) { $params["sni"] } else { $serverHost }
|
||||
$fp = if ($params["fp"]) { $params["fp"] } else { "chrome" }
|
||||
$flow = if ($params["flow"]) { $params["flow"] } else { "" }
|
||||
|
||||
return @{
|
||||
uuid = $uuid
|
||||
server = $serverHost
|
||||
server_port = $port
|
||||
tag = $tag
|
||||
public_key = $pbk
|
||||
short_id = $sid
|
||||
server_name = $sni
|
||||
fingerprint = $fp
|
||||
flow = $flow
|
||||
}
|
||||
}
|
||||
|
||||
# --- MAIN ---
|
||||
|
||||
if ($Debug) { Set-DebugMode -Enabled $true }
|
||||
|
||||
Write-Header "NATIVE SING-BOX (UDP ПОДДЕРЖКА)" -ClearScreen
|
||||
|
||||
$taskStatus = Get-TaskStatus -Name $TaskName
|
||||
|
||||
if ($taskStatus -and -not $Force) {
|
||||
Write-Info "Sing-box уже установлен."
|
||||
Write-Host " Статус: $taskStatus" -ForegroundColor ($taskStatus -eq "Running" ? "Green" : "Red")
|
||||
Write-Host ""
|
||||
|
||||
$opts = [Ordered]@{
|
||||
"1" = "Сменить сервер (из подписки)"
|
||||
"2" = "Ввести новую ссылку на подписку"
|
||||
"3" = "Перезапустить службу"
|
||||
"4" = "Остановить службу"
|
||||
"5" = "Показать конфиг"
|
||||
"6" = "Переустановить"
|
||||
"b" = "Назад"
|
||||
}
|
||||
|
||||
$act = Show-Menu -Options $opts
|
||||
|
||||
switch ($act) {
|
||||
"1" {
|
||||
# Reload existing sub logic could be added here, currently just re-runs install flow partially
|
||||
# Simplification: treat as new setup but try to load saved sub url
|
||||
$Force = $true
|
||||
}
|
||||
"2" { $SubscriptionUrl = ""; $Force = $true }
|
||||
"3" { Manage-ScheduledTask -Name $TaskName -Action "Start"; Write-Success "Запущено!"; exit }
|
||||
"4" { Manage-ScheduledTask -Name $TaskName -Action "Stop"; Write-Success "Остановлено!"; exit }
|
||||
"5" { Get-Content "$InstallDir\config.json"; exit }
|
||||
"6" { $Force = $true }
|
||||
"b" { exit }
|
||||
}
|
||||
}
|
||||
|
||||
if ($Force -or -not $taskStatus) {
|
||||
# 1. Загрузка
|
||||
Write-Step "Установка Sing-box..."
|
||||
if (!(Test-Path "$InstallDir\sing-box.exe")) {
|
||||
New-Item -ItemType Directory -Path $InstallDir -Force | Out-Null
|
||||
$zipCtx = "$env:TEMP\sing-box.zip"
|
||||
if (Download-File -Url $SingboxUrl -Destination $zipCtx) {
|
||||
Expand-Archive -Path $zipCtx -DestinationPath $env:TEMP -Force
|
||||
$extracted = Get-ChildItem "$env:TEMP\sing-box-*" -Directory | Select -First 1
|
||||
Copy-Item "$($extracted.FullName)\sing-box.exe" "$InstallDir\sing-box.exe" -Force
|
||||
Remove-Item $zipCtx; Remove-Item $extracted.FullName -Recurse -Force
|
||||
Write-Success "Sing-box скачан"
|
||||
}
|
||||
else {
|
||||
Read-Host "Нажмите Enter для выхода..."
|
||||
exit 1
|
||||
}
|
||||
}
|
||||
|
||||
# 2. Подписка
|
||||
if ([string]::IsNullOrWhiteSpace($SubscriptionUrl)) {
|
||||
# Try load saved
|
||||
$savedSub = "$InstallDir\sub_info.json"
|
||||
if (Test-Path $savedSub) {
|
||||
try {
|
||||
$json = Get-Content $savedSub -Raw | ConvertFrom-Json
|
||||
if ($json.url) {
|
||||
Write-Info "Найдена сохраненная подписка: $($json.url)"
|
||||
if ((Read-Host "Использовать? (y/n)") -eq 'y') { $SubscriptionUrl = $json.url }
|
||||
}
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SubscriptionUrl)) {
|
||||
$SubscriptionUrl = Read-Host "`n🔗 Введите URL подписки (VLESS)"
|
||||
}
|
||||
|
||||
if ([string]::IsNullOrWhiteSpace($SubscriptionUrl)) {
|
||||
Write-Error "Url не указан"
|
||||
Read-Host "Нажмите Enter для выхода..."
|
||||
exit
|
||||
}
|
||||
|
||||
# --- PARSING ---
|
||||
$data = @{ success = $false; config = $null; error = "" }
|
||||
|
||||
if ($SubscriptionUrl.StartsWith("vless://")) {
|
||||
try {
|
||||
$p = Parse-VlessUrl -Url $SubscriptionUrl
|
||||
$outbound = [Ordered]@{
|
||||
type = "vless"
|
||||
tag = $p.tag
|
||||
server = $p.server
|
||||
server_port = $p.server_port
|
||||
uuid = $p.uuid
|
||||
flow = $p.flow
|
||||
tls = @{
|
||||
enabled = $true
|
||||
server_name = $p.server_name
|
||||
utls = @{ enabled = $true; fingerprint = $p.fingerprint }
|
||||
reality = @{
|
||||
enabled = $true
|
||||
public_key = $p.public_key
|
||||
short_id = $p.short_id
|
||||
}
|
||||
}
|
||||
packet_encoding = "xudp"
|
||||
}
|
||||
$data.success = $true
|
||||
$data.config = @{ outbounds = @($outbound) }
|
||||
}
|
||||
catch {
|
||||
$data.error = $_.Exception.Message
|
||||
}
|
||||
}
|
||||
else {
|
||||
$data = Get-SubscriptionData -Url $SubscriptionUrl -Headers (Get-SubscriptionHeaders)
|
||||
}
|
||||
|
||||
|
||||
# --- PARSING LOGIC ENHANCEMENT ---
|
||||
if (-not $data.success) {
|
||||
# Fallback: Try to handle non-JSON body (Base64 or Plain Text)
|
||||
try {
|
||||
Write-Info "JSON парсинг не удался, пробую как список ссылок..."
|
||||
$content = $data.rawContent
|
||||
|
||||
# Base64 decode if needed
|
||||
if ($content -match "^[A-Za-z0-9+/=]+$") {
|
||||
try {
|
||||
$bytes = [System.Convert]::FromBase64String($content)
|
||||
$content = [System.Text.Encoding]::UTF8.GetString($bytes)
|
||||
}
|
||||
catch {}
|
||||
}
|
||||
|
||||
# Try to find vless:// links
|
||||
$links = $content -split "[\r\n]+" | Where-Object { $_ -match "^vless://" }
|
||||
|
||||
if ($links.Count -gt 0) {
|
||||
Write-Success "Найдено ссылок: $($links.Count)"
|
||||
|
||||
# Mock a config object with these links as "outbounds"
|
||||
# Note: We can't fully parsing VLESS query params in pure PS easily without a lot of regex
|
||||
# So we will try a simpler approach: Let sing-box do it? No, sing-box needs config.
|
||||
|
||||
# WORKAROUND: Create a minimal outbound for each link
|
||||
# Parsing `vless://UUID@HOST:PORT?security=reality&...#NAME`
|
||||
$parsedOutbounds = @()
|
||||
|
||||
foreach ($link in $links) {
|
||||
if ($link -match "vless://([^@]+)@([^:]+):(\d+)(\?.*)?(#.*)?") {
|
||||
$uuid = $matches[1]
|
||||
$server = $matches[2]
|
||||
$port = [int]$matches[3]
|
||||
$query = $matches[4]
|
||||
$hash = $matches[5]
|
||||
|
||||
$tag = if ($hash) { $hash.Substring(1) } else { "${server}:${port}" }
|
||||
$tag = [System.Web.HttpUtility]::UrlDecode($tag)
|
||||
|
||||
# Parse Query Params
|
||||
$flow = ""; $fp = ""; $pbk = ""; $sid = ""; $sni = ""; $serviceName = ""
|
||||
|
||||
if ($query) {
|
||||
if ($query -match "flow=([^&]+)") { $flow = $matches[1] }
|
||||
if ($query -match "fp=([^&]+)") { $fp = $matches[1] }
|
||||
if ($query -match "pbk=([^&]+)") { $pbk = $matches[1] }
|
||||
if ($query -match "sid=([^&]+)") { $sid = $matches[1] }
|
||||
if ($query -match "sni=([^&]+)") { $sni = $matches[1] }
|
||||
if ($query -match "serviceName=([^&]+)") { $serviceName = $matches[1] }
|
||||
}
|
||||
|
||||
# Construct Sing-box outbound (REALITY based assumption for modern vless)
|
||||
$out = [Ordered]@{
|
||||
type = "vless"
|
||||
tag = $tag
|
||||
server = $server
|
||||
server_port = $port
|
||||
uuid = $uuid
|
||||
flow = $flow
|
||||
tls = @{
|
||||
enabled = $true
|
||||
server_name = $sni
|
||||
utls = @{ enabled = $true; fingerprint = $fp }
|
||||
reality = @{
|
||||
enabled = $true
|
||||
public_key = $pbk
|
||||
short_id = $sid
|
||||
}
|
||||
}
|
||||
packet_encoding = "xudp"
|
||||
}
|
||||
$parsedOutbounds += $out
|
||||
}
|
||||
}
|
||||
|
||||
if ($parsedOutbounds.Count -gt 0) {
|
||||
$data.success = $true
|
||||
$data.config = @{ outbounds = $parsedOutbounds }
|
||||
$data.error = $null
|
||||
}
|
||||
else {
|
||||
throw "Не удалось распарсить VLESS ссылки"
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
throw $data.error
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Error "Ошибка обработки подписки: $_"
|
||||
Write-Host " Скрипт поддерживает: SIP008 (JSON) или список VLESS+Reality ссылок." -ForegroundColor Yellow
|
||||
Read-Host "Нажмите Enter для выхода..."
|
||||
exit
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Save sub info
|
||||
@{ url = $SubscriptionUrl } | ConvertTo-Json | Set-Content "$InstallDir\sub_info.json"
|
||||
|
||||
# 3. Выбор сервера
|
||||
$server = Select-Server -Config $data.config
|
||||
if (!$server) {
|
||||
Read-Host "Нажмите Enter для выхода..."
|
||||
exit
|
||||
}
|
||||
|
||||
# 4. Конфиг
|
||||
$cfg = New-SingboxConfig -Outbound $server.outbound -Port $LocalProxyPort
|
||||
$cfg | ConvertTo-Json -Depth 10 | Set-Content "$InstallDir\config.json" -Encoding UTF8
|
||||
|
||||
# 5. Задача
|
||||
Manage-ScheduledTask -Name $TaskName -ExePath "$InstallDir\sing-box.exe" -Arguments "run -c `"$InstallDir\config.json`"" -WorkDir $InstallDir -Action "Install"
|
||||
Manage-ScheduledTask -Name $TaskName -Action "Start"
|
||||
|
||||
# 6. Firewall
|
||||
if (Ensure-FirewallPort -Port $LocalProxyPort -Name "SingBox-Proxy-Port") {
|
||||
Write-Success "Правило Firewall создано (порт $LocalProxyPort)"
|
||||
}
|
||||
|
||||
Write-Success "Успешно установлено и запущено!"
|
||||
Write-Info "Локальный прокси: 127.0.0.1:$LocalProxyPort"
|
||||
|
||||
$ips = Get-LocalIPs
|
||||
if ($ips) {
|
||||
Write-Info "Доступно из сети по адресам:"
|
||||
foreach ($ip in $ips) {
|
||||
Write-Host " ${ip}:$LocalProxyPort" -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
Start-Sleep -Seconds 3
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
# ==========================================
|
||||
# 🗑️ UNINSTALL ALL (CLEANUP)
|
||||
# ==========================================
|
||||
|
||||
param([switch]$Debug)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$ScriptDir\lib\Common.ps1"
|
||||
. "$ScriptDir\lib\System.ps1"
|
||||
|
||||
if ($Debug) { Set-DebugMode -Enabled $true }
|
||||
|
||||
Write-Header "ПОЛНОЕ УДАЛЕНИЕ" -ClearScreen
|
||||
|
||||
Ensure-Admin
|
||||
|
||||
Write-Warning "Это действие удалит весь установленный софт:"
|
||||
Write-Host " - Sing-box (Служба и файлы)" -ForegroundColor Gray
|
||||
Write-Host " - ProxiFyre (Служба и файлы)" -ForegroundColor Gray
|
||||
Write-Host " - Драйвер WinPacketFilter" -ForegroundColor Gray
|
||||
Write-Host ""
|
||||
|
||||
if ((Read-Host "Вы уверены? (y/n)") -ne 'y') { exit }
|
||||
|
||||
Write-Step "Удаление Sing-box..."
|
||||
Manage-ScheduledTask -Name "SingBoxProxy" -Action "Stop"
|
||||
Manage-ScheduledTask -Name "SingBoxProxy" -Action "Uninstall"
|
||||
|
||||
if (Test-Path "C:\Tools\sing-box") {
|
||||
Remove-Item "C:\Tools\sing-box" -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Success "Файлы удалены"
|
||||
}
|
||||
|
||||
Write-Step "Удаление Discord Proxy (ProxiFyre)..."
|
||||
$pfDir = "C:\Tools\ProxiFyre"
|
||||
if (Test-Path "$pfDir\ProxiFyre.exe") {
|
||||
if (Get-DebugMode) {
|
||||
& "$pfDir\ProxiFyre.exe" uninstall
|
||||
}
|
||||
else {
|
||||
& "$pfDir\ProxiFyre.exe" uninstall 2>&1 | Out-Null
|
||||
}
|
||||
Start-Sleep -Seconds 2
|
||||
Write-Success "Служба удалена"
|
||||
}
|
||||
|
||||
if (Test-Path $pfDir) {
|
||||
Remove-Item $pfDir -Recurse -Force -ErrorAction SilentlyContinue
|
||||
Write-Success "Файлы удалены"
|
||||
}
|
||||
|
||||
Write-Step "Удаление драйвера..."
|
||||
# Тут сложно удалить MSI тихо без GUID, но попробуем через known path или пропустим, т.к. драйвер может быть нужен другим
|
||||
Write-Info "Драйвер WinPacketFilter оставлен (он может использоваться другим ПО)."
|
||||
Write-Info "Если нужно, удалите его через 'Установка и удаление программ'."
|
||||
|
||||
Write-Success "Очистка завершена!"
|
||||
Start-Sleep -Seconds 3
|
||||
@@ -1,121 +0,0 @@
|
||||
# ==========================================
|
||||
# 📜 LOG VIEWER
|
||||
# ==========================================
|
||||
# View logs from sing-box and ProxiFyre
|
||||
|
||||
param([switch]$Follow)
|
||||
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
. "$ScriptDir\lib\Common.ps1"
|
||||
|
||||
$SingboxLog = "C:\Tools\sing-box\singbox.log"
|
||||
$ProxiFyreLog = "C:\Tools\ProxiFyre"
|
||||
|
||||
function Show-LogFile {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string]$Title,
|
||||
[int]$Lines = 30,
|
||||
[string]$Color = "Gray"
|
||||
)
|
||||
|
||||
if (Test-Path $Path) {
|
||||
Write-Host "`n═══ $Title ═══" -ForegroundColor Cyan
|
||||
$content = Get-Content $Path -Tail $Lines -ErrorAction SilentlyContinue
|
||||
if ($content) {
|
||||
$content | ForEach-Object { Write-Host " $_" -ForegroundColor $Color }
|
||||
}
|
||||
else {
|
||||
Write-Host " (Лог пустой)" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
else {
|
||||
Write-Host "`n═══ $Title ═══" -ForegroundColor Cyan
|
||||
Write-Host " (Файл не найден: $Path)" -ForegroundColor DarkGray
|
||||
}
|
||||
}
|
||||
|
||||
function Tail-Logs {
|
||||
Write-Host "`n📜 Режим отслеживания логов (Ctrl+C для выхода)" -ForegroundColor Yellow
|
||||
Write-Host " sing-box: $SingboxLog" -ForegroundColor DarkGray
|
||||
Write-Host " ProxiFyre: $ProxiFyreLog\*.log" -ForegroundColor DarkGray
|
||||
Write-Host ""
|
||||
|
||||
$sbPos = 0
|
||||
$pfPos = 0
|
||||
$pfLogFile = $null
|
||||
|
||||
# Initial positions
|
||||
if (Test-Path $SingboxLog) { $sbPos = (Get-Item $SingboxLog).Length }
|
||||
$pfLogFile = Get-ChildItem "$ProxiFyreLog\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1
|
||||
if ($pfLogFile) { $pfPos = $pfLogFile.Length }
|
||||
|
||||
try {
|
||||
while ($true) {
|
||||
Start-Sleep -Milliseconds 500
|
||||
|
||||
# Sing-box
|
||||
if (Test-Path $SingboxLog) {
|
||||
$newSize = (Get-Item $SingboxLog).Length
|
||||
if ($newSize -gt $sbPos) {
|
||||
$content = Get-Content $SingboxLog -Tail 20 -ErrorAction SilentlyContinue
|
||||
# Show only new lines (approximate)
|
||||
$content | Select-Object -Last ([math]::Max(1, [math]::Ceiling(($newSize - $sbPos) / 100))) | ForEach-Object {
|
||||
Write-Host "[SB] $_" -ForegroundColor Green
|
||||
}
|
||||
$sbPos = $newSize
|
||||
}
|
||||
}
|
||||
|
||||
# ProxiFyre
|
||||
$pfLogFile = Get-ChildItem "$ProxiFyreLog\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1
|
||||
if ($pfLogFile) {
|
||||
$newSize = $pfLogFile.Length
|
||||
if ($newSize -gt $pfPos) {
|
||||
$content = Get-Content $pfLogFile.FullName -Tail 20 -ErrorAction SilentlyContinue
|
||||
$content | Select-Object -Last ([math]::Max(1, [math]::Ceiling(($newSize - $pfPos) / 100))) | ForEach-Object {
|
||||
Write-Host "[PF] $_" -ForegroundColor Yellow
|
||||
}
|
||||
$pfPos = $newSize
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch {
|
||||
Write-Host "`nОстановлено." -ForegroundColor Gray
|
||||
}
|
||||
}
|
||||
|
||||
# --- MAIN ---
|
||||
|
||||
Write-Header "ПРОСМОТР ЛОГОВ" -ClearScreen
|
||||
|
||||
$opts = [Ordered]@{
|
||||
"1" = "Показать последние логи"
|
||||
"2" = "Следить за логами в реальном времени (tail -f)"
|
||||
"b" = "Назад"
|
||||
}
|
||||
|
||||
$choice = Show-Menu -Options $opts
|
||||
|
||||
switch ($choice) {
|
||||
"1" {
|
||||
Show-LogFile -Path $SingboxLog -Title "SING-BOX (VPN)" -Lines 50 -Color "Green"
|
||||
|
||||
$pfLog = Get-ChildItem "$ProxiFyreLog\*.log" -ErrorAction SilentlyContinue | Sort-Object LastWriteTime | Select-Object -Last 1
|
||||
if ($pfLog) {
|
||||
Show-LogFile -Path $pfLog.FullName -Title "PROXIFYRE (Discord)" -Lines 50 -Color "Yellow"
|
||||
}
|
||||
else {
|
||||
Write-Host "`n═══ PROXIFYRE (Discord) ═══" -ForegroundColor Cyan
|
||||
Write-Host " (Логов не найдено в $ProxiFyreLog)" -ForegroundColor DarkGray
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Read-Host "Нажмите Enter для выхода"
|
||||
}
|
||||
"2" {
|
||||
Tail-Logs
|
||||
}
|
||||
"b" { exit }
|
||||
}
|
||||
Reference in New Issue
Block a user