Last active
October 30, 2025 15:04
-
-
Save Albus/6656d721c230a27c056575b78c4e3c61 to your computer and use it in GitHub Desktop.
Континент-АП vpn-клиент для macOS 26.1
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env -S -- sudo --prompt пароль: --bell -- pwsh -NoLogo -NoProfile | |
| #Requires -Version 7.4 | |
| #Requires -RunAsAdministrator | |
| #Requires -PSEdition Core | |
| # скрипт предназначен для macOS 26 | |
| # Проверка ОС и версии PowerShell | |
| if (-not $IsMacOS) { | |
| Write-Error "СКРИПТ ТОЛЬКО ДЛЯ macOS!" -ForegroundColor Red | |
| Write-Host "Текущая система: $($PSVersionTable.OS)" -ForegroundColor Yellow | |
| Write-Host "Платформа: $($PSVersionTable.Platform)" -ForegroundColor Yellow | |
| exit 1 | |
| } | |
| # Дополнительная проверка версии macOS (если нужно) | |
| $osVersion = sw_vers -productVersion | |
| Write-Host "macOS версия: $osVersion" -ForegroundColor Cyan | |
| # Проверяем что запускаем из sudo но не от root | |
| if ($null -eq $env:SUDO_USER -and $env:USER -ne "root") { | |
| Write-Host "Программа должна запускаться через sudo. Перезапускаем с sudo..." -ForegroundColor Yellow | |
| $scriptPath = $MyInvocation.MyCommand.Path | |
| sudo pwsh -File $scriptPath @args | |
| exit $LASTEXITCODE | |
| } | |
| if ($env:USER -eq "root" -and $null -eq $env:SUDO_USER) { | |
| Write-Error "Не запускайте напрямую от root, используйте sudo от обычного пользователя" | |
| exit 1 | |
| } | |
| # Полный путь к демону | |
| $DaemonPath = "/usr/local/share/cts/bin/ctsd" | |
| $ClientPath = "/usr/local/bin/cts" | |
| $PidFile = "/var/run/ctsd.pid" | |
| # Упрощенная функция для остановки процессов через PowerShell | |
| function Stop-ProcessSafe { | |
| param( | |
| [int[]]$ProcessIds, | |
| [string]$ProcessName, | |
| [string]$FilePath | |
| ) | |
| $results = @() | |
| # Останавливаем по ProcessIds | |
| if ($ProcessIds) { | |
| foreach ($ppid in $ProcessIds) { | |
| try { | |
| Write-Host "Останавливаем процесс PID: $ppid" | |
| Stop-Process -Id $ppid -Force -ErrorAction Stop | |
| Write-Host "Процесс $ppid остановлен" -ForegroundColor Green | |
| $results += @{ | |
| ProcessId = $ppid | |
| Success = $true | |
| } | |
| } | |
| catch [System.InvalidOperationException] { | |
| # Процесс уже завершен - это не ошибка | |
| Write-Host "Процесс $ppid уже завершен" -ForegroundColor Gray | |
| $results += @{ | |
| ProcessId = $ppid | |
| Success = $true | |
| } | |
| } | |
| catch { | |
| Write-Warning "Не удалось остановить процесс $ppid : $($_.Exception.Message)" | |
| $results += @{ | |
| ProcessId = $ppid | |
| Success = $false | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| } | |
| } | |
| # Останавливаем по имени процесса | |
| if ($ProcessName) { | |
| try { | |
| $processes = Get-Process -Name $ProcessName -ErrorAction SilentlyContinue | |
| foreach ($process in $processes) { | |
| try { | |
| Write-Host "Останавливаем процесс: $($process.ProcessName) (PID: $($process.Id))" | |
| Stop-Process -Id $process.Id -Force -ErrorAction Stop | |
| Write-Host "Процесс $($process.ProcessName) остановлен" -ForegroundColor Green | |
| $results += @{ | |
| ProcessId = $process.Id | |
| ProcessName = $process.ProcessName | |
| Success = $true | |
| } | |
| } | |
| catch [System.InvalidOperationException] { | |
| Write-Host "Процесс $($process.ProcessName) уже завершен" -ForegroundColor Gray | |
| $results += @{ | |
| ProcessId = $process.Id | |
| ProcessName = $process.ProcessName | |
| Success = $true | |
| } | |
| } | |
| catch { | |
| Write-Warning "Не удалось остановить процесс $($process.ProcessName): $($_.Exception.Message)" | |
| $results += @{ | |
| ProcessId = $process.Id | |
| ProcessName = $process.ProcessName | |
| Success = $false | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Debug "Процессы с именем $ProcessName не найдены" | |
| } | |
| } | |
| return $results | |
| } | |
| # Функция для расширенного запуска процесса | |
| function Start-ProcessExtended { | |
| param( | |
| [string]$FilePath, | |
| [string]$ArgumentList = "", | |
| [string]$WorkingDirectory = "", | |
| [hashtable]$EnvironmentVariables = @{}, | |
| [switch]$WaitForExit, | |
| [int]$TimeoutSeconds = 30 | |
| ) | |
| $processInfo = New-Object System.Diagnostics.ProcessStartInfo | |
| $processInfo.FileName = $FilePath | |
| $processInfo.Arguments = $ArgumentList | |
| $processInfo.RedirectStandardOutput = $true | |
| $processInfo.RedirectStandardError = $true | |
| $processInfo.UseShellExecute = $false | |
| $processInfo.CreateNoWindow = $true | |
| if ($WorkingDirectory) { | |
| $processInfo.WorkingDirectory = $WorkingDirectory | |
| } | |
| # Добавляем переменные окружения | |
| foreach ($key in $EnvironmentVariables.Keys) { | |
| $processInfo.EnvironmentVariables[$key] = $EnvironmentVariables[$key] | |
| } | |
| $process = New-Object System.Diagnostics.Process | |
| $process.StartInfo = $processInfo | |
| Write-Host "Запуск процесса: $FilePath $ArgumentList" -ForegroundColor Gray | |
| try { | |
| $started = $process.Start() | |
| if (-not $started) { | |
| throw "Не удалось запустить процесс" | |
| } | |
| if ($WaitForExit) { | |
| $completed = $process.WaitForExit($TimeoutSeconds * 1000) | |
| if (-not $completed) { | |
| Write-Warning "Процесс не завершился за $TimeoutSeconds секунд, принудительное завершение" | |
| try { | |
| $process.Kill() | |
| $process.WaitForExit(5000) | |
| } | |
| catch { | |
| # Игнорируем ошибки при завершении | |
| } | |
| return @{ | |
| Success = $false | |
| ExitCode = -1 | |
| Output = "" | |
| Error = "Таймаут выполнения" | |
| } | |
| } | |
| $output = $process.StandardOutput.ReadToEnd() | |
| $errorOutput = $process.StandardError.ReadToEnd() | |
| return @{ | |
| Success = ($process.ExitCode -eq 0) | |
| ExitCode = $process.ExitCode | |
| Output = $output.Trim() | |
| Error = $errorOutput.Trim() | |
| } | |
| } else { | |
| # Для фоновых процессов | |
| Start-Sleep -Milliseconds 500 | |
| return @{ | |
| Success = $true | |
| ProcessId = $process.Id | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Error "Ошибка запуска процесса: $($_.Exception.Message)" | |
| return @{ | |
| Success = $false | |
| ExitCode = -1 | |
| Output = "" | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| finally { | |
| if (-not $WaitForExit) { | |
| try { | |
| $process.Dispose() | |
| } | |
| catch { | |
| # Игнорируем ошибки при очистке | |
| } | |
| } | |
| } | |
| } | |
| # Функция для выполнения команды от имени пользователя | |
| function Invoke-AsUser { | |
| param( | |
| [string]$Command, | |
| [string]$Arguments = "", | |
| [string]$User = $env:SUDO_USER | |
| ) | |
| Write-Host "Выполнение от пользователя $User : $Command $Arguments" -ForegroundColor Gray | |
| try { | |
| # Используем Start-Process для запуска от имени пользователя | |
| $processInfo = New-Object System.Diagnostics.ProcessStartInfo | |
| $processInfo.FileName = "sudo" | |
| $processInfo.Arguments = "-u $User $Command $Arguments" | |
| $processInfo.RedirectStandardOutput = $true | |
| $processInfo.RedirectStandardError = $true | |
| $processInfo.UseShellExecute = $false | |
| $processInfo.CreateNoWindow = $true | |
| $process = New-Object System.Diagnostics.Process | |
| $process.StartInfo = $processInfo | |
| $process.Start() | Out-Null | |
| $process.WaitForExit(30000) | Out-Null | |
| $output = $process.StandardOutput.ReadToEnd() | |
| $errorOutput = $process.StandardError.ReadToEnd() | |
| return @{ | |
| Success = ($process.ExitCode -eq 0) | |
| ExitCode = $process.ExitCode | |
| Output = $output.Trim() | |
| Error = $errorOutput.Trim() | |
| } | |
| } | |
| catch { | |
| Write-Error "Ошибка выполнения команды: $($_.Exception.Message)" | |
| return @{ | |
| Success = $false | |
| ExitCode = -1 | |
| Output = "" | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| } | |
| # Функция для поиска процессов по исполняемому файлу | |
| function Get-ProcessesByExecutable { | |
| param( | |
| [string]$ExecutablePath | |
| ) | |
| $processes = @() | |
| try { | |
| # Получаем все процессы | |
| $allProcesses = Get-Process -ErrorAction SilentlyContinue | |
| foreach ($process in $allProcesses) { | |
| try { | |
| # Проверяем путь к исполняемому файлу процесса | |
| if ($process.Path -eq $ExecutablePath) { | |
| $processes += $process | |
| } | |
| } | |
| catch { | |
| # Игнорируем процессы, к которым нет доступа | |
| continue | |
| } | |
| } | |
| } | |
| catch { | |
| Write-Debug "Ошибка при поиске процессов: $($_.Exception.Message)" | |
| } | |
| return $processes | |
| } | |
| # Функция для полного уничтожения демона | |
| function Stop-Daemon { | |
| Write-Host "Остановка демона..." | |
| # Пытаемся остановить соединение от имени пользователя | |
| $result = Invoke-AsUser -Command $ClientPath -Arguments "disconnect" | |
| if (-not $result.Success) { | |
| Write-Warning "Не удалось выполнить disconnect: $($result.Error)" | |
| } | |
| Start-Sleep -Seconds 2 | |
| # Пытаемся остановить демона | |
| $result = Start-ProcessExtended -FilePath $DaemonPath -ArgumentList "stop" -WaitForExit -TimeoutSeconds 10 | |
| if (-not $result.Success) { | |
| Write-Warning "Не удалось остановить демон: $($result.Error)" | |
| } | |
| Start-Sleep -Seconds 2 | |
| # Ищем процессы только по исполняемому файлу ctsd | |
| $ctsdProcesses = @() | |
| try { | |
| $ctsdProcesses = Get-ProcessesByExecutable -ExecutablePath $DaemonPath | |
| # Дополнительно ищем по имени процесса, если по пути не найдено | |
| if ($ctsdProcesses.Count -eq 0) { | |
| Write-Host "Поиск процессов демона по имени..." | |
| $ctsdProcesses = Get-Process -Name "ctsd" -ErrorAction SilentlyContinue | |
| } | |
| } | |
| catch { | |
| Write-Debug "Ошибка при поиске процессов: $($_.Exception.Message)" | |
| } | |
| if ($ctsdProcesses) { | |
| Write-Host "Найдены процессы демона ($($ctsdProcesses.Count)), останавливаем..." -ForegroundColor Yellow | |
| # Останавливаем процессы через безопасную функцию | |
| $processIds = $ctsdProcesses | ForEach-Object { $_.Id } | |
| $stopResults = Stop-ProcessSafe -ProcessIds $processIds | |
| $failedProcesses = $stopResults | Where-Object { -not $_.Success } | |
| if ($failedProcesses) { | |
| Write-Warning "Не удалось остановить некоторые процессы демона" | |
| # Дополнительная проверка | |
| Start-Sleep -Seconds 2 | |
| $remaining = Get-ProcessesByExecutable -ExecutablePath $DaemonPath | |
| if ($remaining) { | |
| Write-Error "Не удалось уничтожить все процессы демона" | |
| foreach ($proc in $remaining) { | |
| Write-Host "Оставшийся процесс: $($proc.Id) $($proc.ProcessName)" | |
| } | |
| } | |
| } else { | |
| Write-Host "Все процессы демона остановлены" -ForegroundColor Green | |
| } | |
| } else { | |
| Write-Host "Процессы демона не найдены" -ForegroundColor Green | |
| } | |
| # Удаляем PID файл если существует | |
| if (Test-Path $PidFile) { | |
| try { | |
| Remove-Item $PidFile -Force -ErrorAction SilentlyContinue | |
| Write-Host "PID файл удален: $PidFile" | |
| } | |
| catch { | |
| Write-Debug "Не удалось удалить PID файл: $($_.Exception.Message)" | |
| } | |
| } | |
| } | |
| function Find-InterfaceForIP { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string]$IPAddress | |
| ) | |
| Write-Host "🔍 Поиск интерфейса для IP: $IPAddress" -ForegroundColor Magenta | |
| # Проверяем валидность IP | |
| $validIP = [System.Net.IPAddress]::TryParse($IPAddress, [ref]$null) | |
| if (-not $validIP) { | |
| Write-Error "Некорректный IP адрес: $IPAddress" | |
| return $null | |
| } | |
| # Метод 1: через route get (самый надежный на macOS) | |
| Write-Host "`n📡 Метод 1: Анализ таблицы маршрутизации..." -ForegroundColor Cyan | |
| $routeResult = & { route get $IPAddress 2>$null } | |
| if ($LASTEXITCODE -eq 0) { | |
| $interfaceLine = $routeResult | Select-String "interface:" | |
| if ($interfaceLine) { | |
| $interface = ($interfaceLine -split ":")[1].Trim() | |
| Write-Host "✅ Найден интерфейс: $interface" -ForegroundColor Green | |
| return $interface | |
| } | |
| } | |
| # Метод 2: через анализ сетевых интерфейсов | |
| Write-Host "`n🔧 Метод 2: Анализ сетевых интерфейсов..." -ForegroundColor Cyan | |
| $interfaces = Get-NetIPInterface | Where-Object { $_.ConnectionState -eq "Connected" } | |
| foreach ($if in $interfaces) { | |
| $addresses = Get-NetIPAddress -InterfaceIndex $if.InterfaceIndex -AddressFamily IPv4 | | |
| Where-Object { $_.IPAddress -ne "127.0.0.1" } | |
| foreach ($addr in $addresses) { | |
| # Упрощенная проверка принадлежности к подсети | |
| $networkInfo = "$($addr.IPAddress)/$($addr.PrefixLength)" | |
| Write-Host " 🔍 Проверка $networkInfo..." -ForegroundColor Gray | |
| # Здесь можно добавить более сложную логику проверки подсети | |
| if ($addr.IPAddress -eq $IPAddress) { | |
| Write-Host "✅ IP принадлежит интерфейсу: $($if.InterfaceAlias)" -ForegroundColor Green | |
| return $if.InterfaceAlias | |
| } | |
| } | |
| } | |
| Write-Host "❌ Не удалось определить интерфейс для $IPAddress" -ForegroundColor Red | |
| return $null | |
| } | |
| # Быстрая функция проверки соединения через .NET Ping (самый быстрый метод) | |
| function Test-ConnectionFast { | |
| param( | |
| [int]$TimeoutMs = 800 # Уменьшенный таймаут для быстрой проверки | |
| ) | |
| $target = "10.197.64.6" | |
| try { | |
| $ping = New-Object System.Net.NetworkInformation.Ping | |
| $reply = $ping.Send($target, $TimeoutMs) | |
| $result = ($reply -and $reply.Status -eq [System.Net.NetworkInformation.IPStatus]::Success) | |
| if ($result) { | |
| return @{ | |
| Success = $true | |
| ResponseTime = $reply.RoundtripTime | |
| } | |
| } else { | |
| return @{ | |
| Success = $false | |
| ResponseTime = 0 | |
| } | |
| } | |
| } | |
| catch { | |
| return @{ | |
| Success = $false | |
| ResponseTime = 0 | |
| Error = $_.Exception.Message | |
| } | |
| } | |
| finally { | |
| try { if ($ping) { $ping.Dispose() } } catch { } | |
| } | |
| } | |
| # Основная функция проверки соединения (быстрая версия) | |
| function Test-Connection { | |
| $result = Test-ConnectionFast -TimeoutMs 500 | |
| return $result.Success | |
| } | |
| # Функция для установки соединения | |
| function Connect-VPN { | |
| Write-Host "Установка VPN соединения..." -ForegroundColor Cyan | |
| # Сначала останавливаем демона (из-за бага) | |
| Stop-Daemon | |
| # Запускаем демона в фоновом режиме | |
| Write-Host "Запуск демона VPN..." -ForegroundColor Cyan | |
| $result = Start-ProcessExtended -FilePath $DaemonPath -WaitForExit -TimeoutSeconds 10 | |
| if (-not $result.Success) { | |
| Write-Warning "Демон завершился с ошибкой: $($result.Error)" | |
| } else { | |
| Write-Host "Демон запущен успешно" -ForegroundColor Green | |
| } | |
| Start-Sleep -Seconds 3 # Уменьшено с 5 до 3 секунд | |
| # Устанавливаем соединение от имени пользователя | |
| Write-Host "Установка VPN соединения..." -ForegroundColor Cyan | |
| $result = Invoke-AsUser -Command $ClientPath -Arguments "connect" | |
| if ($result.Success) { | |
| Write-Host "Команда connect выполнена успешно" -ForegroundColor Green | |
| if ($result.Output) { | |
| Write-Host "Вывод: $($result.Output)" -ForegroundColor Gray | |
| } | |
| } else { | |
| Write-Warning "Команда connect завершилась с ошибкой: $($result.Error)" | |
| } | |
| Start-Sleep -Seconds 2 # Уменьшено с 3 до 2 секунд | |
| # Проверяем соединение | |
| if (Test-Connection) { | |
| Write-Host "VPN соединение установлено успешно" -ForegroundColor Green | |
| return $true | |
| } else { | |
| Write-Warning "Соединение установлено, но DNS недоступен" | |
| return $false | |
| } | |
| } | |
| # Обработчик Ctrl+C | |
| try { | |
| $null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action { | |
| Write-Host "`nЗавершение работы..." -ForegroundColor Yellow | |
| Stop-Daemon | |
| exit 0 | |
| } | |
| } | |
| catch { | |
| Write-Warning "Не удалось зарегистрировать обработчик exiring: $($_.Exception.Message)" | |
| } | |
| # Обработчик SIGTERM (сигнал завершения) | |
| try { | |
| $null = Register-EngineEvent -SourceIdentifier Signal:Terminate -Action { | |
| Write-Host "`nПолучен сигнал завершения (SIGTERM)..." -ForegroundColor Yellow | |
| $Global:IsShuttingDown = $true | |
| Stop-Daemon | |
| exit 0 | |
| } | |
| } | |
| catch { | |
| Write-Warning "Не удалось зарегистрировать обработчик SIGTERM: $($_.Exception.Message)" | |
| } | |
| function Remove-DNSFromSingleResolv { | |
| param( | |
| [string]$FilePath, | |
| [string]$DNSToRemove | |
| ) | |
| if (-not (Test-Path $FilePath)) { | |
| return | |
| } | |
| $content = Get-Content $FilePath | |
| $originalCount = ($content | Where-Object { $_ -match "^nameserver" }).Count | |
| # Фильтруем DNS серверы | |
| $newContent = $content | Where-Object { $_ -notmatch "^nameserver\s+$DNSToRemove$" } | |
| $newCount = ($newContent | Where-Object { $_ -match "^nameserver" }).Count | |
| if ($originalCount -eq $newCount) { | |
| return | |
| } | |
| # Создаем бэкап | |
| $backupPath = "$FilePath.backup.$(Get-Date -Format 'HHmmss')" | |
| Copy-Item $FilePath $backupPath | |
| # Сохраняем изменения | |
| $newContent | Out-File $FilePath -Encoding ASCII | |
| $removedCount = $originalCount - $newCount | |
| Write-Host "✅ Удалено $removedCount вхождений DNS из $FilePath" -ForegroundColor Green | |
| } | |
| function Remove-DNSFromAllResolvFiles { | |
| param( | |
| [Parameter(Mandatory=$true)] | |
| [string]$DNSToRemove | |
| ) | |
| if ((id -u) -ne 0) { | |
| Write-Host "🔒 Требуются права root" -ForegroundColor Red | |
| return | |
| } | |
| # Список возможных расположений resolv.conf | |
| $resolvPaths = @( | |
| "/var/run/resolv.conf", | |
| "/etc/resolv.conf", | |
| "/private/var/run/resolv.conf" | |
| ) | |
| foreach ($path in $resolvPaths) { | |
| if (Test-Path $path) { | |
| $realPath = $path | |
| # Проверяем симлинки | |
| $item = Get-Item $path -ErrorAction SilentlyContinue | |
| if ($item -and $item.LinkType -eq "SymbolicLink") { | |
| $realPath = $item.Target | |
| } | |
| # Удаляем DNS из файла | |
| Remove-DNSFromSingleResolv -FilePath $realPath -DNSToRemove $DNSToRemove | |
| } | |
| } | |
| } | |
| # Основной цикл программы | |
| try { | |
| Write-Host "Запуск VPN монитора..." -ForegroundColor Yellow | |
| Write-Host "Для выхода нажмите Ctrl+C" -ForegroundColor Yellow | |
| Write-Host "Быстрый мониторинг соединения (1 сек)" -ForegroundColor Cyan | |
| while ($true) { | |
| # Устанавливаем соединение | |
| $connected = Connect-VPN | |
| if (-not $connected) { | |
| Write-Host "Попытка переподключения через 3 секунды..." -ForegroundColor Yellow | |
| Start-Sleep -Seconds 3 | |
| continue | |
| } | |
| # Мониторинг соединения | |
| $interface = Find-InterfaceForIP -IPAddress "10.197.64.6" | |
| ifconfig $interface | |
| "10.99.0.35","10.197.64.6" | ForEach-Object { Remove-DNSFromAllResolvFiles -DNSToRemove $_ } | |
| Write-Host "🧹 Очистка DNS кэша..." -ForegroundColor Yellow | |
| dscacheutil -flushcache | |
| killall -HUP mDNSResponder | |
| Write-Host "Мониторинг соединения 10.197.64.6..." -ForegroundColor Cyan | |
| $failureCount = 0 | |
| $successCount = 0 | |
| $lastStatus = $true | |
| while ($true) { | |
| # Быстрая проверка каждую секунду | |
| Start-Sleep -Seconds 1 | |
| $connectionResult = Test-ConnectionFast -TimeoutMs 800 | |
| $isConnected = $connectionResult.Success | |
| if (-not $isConnected) { | |
| $failureCount++ | |
| $successCount = 0 | |
| # Выводим предупреждение только при изменении статуса или каждые 5 неудач | |
| if ($lastStatus -or $failureCount -eq 1 -or $failureCount % 5 -eq 0) { | |
| Write-Warning "Потеряно соединение с DNS ($failureCount/3)" | |
| } | |
| if ($failureCount -ge 3) { | |
| Write-Host "Перезапуск VPN соединения..." -ForegroundColor Yellow | |
| break | |
| } | |
| } else { | |
| $successCount++ | |
| # Выводим сообщение о восстановлении только при изменении статуса | |
| if (-not $lastStatus) { | |
| Write-Host "Соединение восстановлено (ping: $($connectionResult.ResponseTime)ms)" -ForegroundColor Green | |
| } | |
| # Периодический статус (каждые 30 успешных проверок) | |
| if ($successCount % 30 -eq 0) { | |
| "10.99.0.35","10.197.64.6" | ForEach-Object { Remove-DNSFromAllResolvFiles -DNSToRemove $_ } | |
| Write-Host "Соединение стабильно - $(Get-Date -Format 'HH:mm:ss') (ping: $($connectionResult.ResponseTime)ms)" -ForegroundColor Gray | |
| } | |
| $failureCount = 0 | |
| } | |
| $lastStatus = $isConnected | |
| } | |
| Write-Host "Переподключение через 2 секунды..." -ForegroundColor Yellow | |
| Start-Sleep -Seconds 2 | |
| } | |
| } | |
| catch { | |
| Write-Error "Критическая ошибка: $_" | |
| try { | |
| Stop-Daemon | |
| } | |
| catch { | |
| Write-Host "Ошибка при остановке демона: $($_.Exception.Message)" -ForegroundColor Red | |
| } | |
| exit 1 | |
| } |
Author
Author
скрипт очищает неверные настройки dns, установленные vpn-клиентом
для разрешения имен нужно 1 раз создать настройку резолвера
sudo mkdir -p /etc/resolver
sudo tee /etc/resolver/domain.local > /dev/null << EOF
nameserver 1.1.1.1
nameserver 1.0.0.1
timeout 5
EOF
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment

Uh oh!
There was an error while loading. Please reload this page.