feat: Реализована нативная установка sing-box с системными утилитами и веб-сервером, заменяя устаревшие скрипты.
This commit is contained in:
59
scripts/lib/Common.ps1
Normal file
59
scripts/lib/Common.ps1
Normal file
@@ -0,0 +1,59 @@
|
||||
# ==========================================
|
||||
# 🛠️ COMMON UTILS
|
||||
# ==========================================
|
||||
|
||||
# --- ЦВЕТА И ВЫВОД ---
|
||||
|
||||
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-Header {
|
||||
param($Title)
|
||||
Write-Host ""
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host " $Title" -ForegroundColor Cyan
|
||||
Write-Host "==========================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
}
|
||||
|
||||
# --- ПОЛЕЗНЫЕ ФУНКЦИИ ---
|
||||
|
||||
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"
|
||||
}
|
||||
|
||||
|
||||
140
scripts/lib/Net.ps1
Normal file
140
scripts/lib/Net.ps1
Normal file
@@ -0,0 +1,140 @@
|
||||
# ==========================================
|
||||
# 🌐 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()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
156
scripts/lib/System.ps1
Normal file
156
scripts/lib/System.ps1
Normal file
@@ -0,0 +1,156 @@
|
||||
# ==========================================
|
||||
# 🖥️ SYSTEM UTILS
|
||||
# ==========================================
|
||||
|
||||
# --- СИСТЕМНАЯ ИНФОРМАЦИЯ ---
|
||||
|
||||
function Get-HWID {
|
||||
param([string]$StoreDir)
|
||||
|
||||
$hwidFile = "$StoreDir\hwid"
|
||||
if (Test-Path $hwidFile) {
|
||||
return (Get-Content $hwidFile -Raw).Trim()
|
||||
}
|
||||
|
||||
$hwid = [System.Guid]::NewGuid().ToString("N").Substring(0, 16)
|
||||
|
||||
if (!(Test-Path $StoreDir)) {
|
||||
New-Item -ItemType Directory -Path $StoreDir -Force | Out-Null
|
||||
}
|
||||
|
||||
Set-Content -Path $hwidFile -Value $hwid
|
||||
return $hwid
|
||||
}
|
||||
|
||||
function Get-SystemInfo {
|
||||
return @{
|
||||
os = "windows"
|
||||
version = [System.Environment]::OSVersion.Version.Major.ToString()
|
||||
}
|
||||
}
|
||||
|
||||
# --- 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
|
||||
}
|
||||
Reference in New Issue
Block a user