|
|
|
@@ -5,13 +5,6 @@ use crate::adapters::proxy_router::{
|
|
|
|
|
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
|
|
|
|
ProxyRouterRequest,
|
|
|
|
|
};
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
use crate::proxifyre::ProxiFyreAdapter;
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
use crate::proxy_router::{
|
|
|
|
|
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
|
|
|
|
ProxyRouterRequest,
|
|
|
|
|
};
|
|
|
|
|
use crate::component_detection::{
|
|
|
|
|
detect_proxyfier_install, detect_proxyfier_install_with_host,
|
|
|
|
|
proxyfier_component_from_detection, DetectedProxyfier, ProxyfierDetectionHost,
|
|
|
|
@@ -22,9 +15,17 @@ use crate::models::{
|
|
|
|
|
ProfileInput, ProfileItem, ProfileItemInput, ProfileItemType, Protocol, ProxyProtocol, Target,
|
|
|
|
|
TargetInput, TargetKind,
|
|
|
|
|
};
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
use crate::proxifyre::ProxiFyreAdapter;
|
|
|
|
|
#[cfg(test)]
|
|
|
|
|
use crate::proxy_router::{
|
|
|
|
|
ProxyRouterAdapter, ProxyRouterError, ProxyRouterErrorKind, ProxyRouterGeneratedConfig,
|
|
|
|
|
ProxyRouterRequest,
|
|
|
|
|
};
|
|
|
|
|
use crate::storage::{default_config_root, JsonStorage};
|
|
|
|
|
use crate::validation::{normalize_profile, normalize_target, ValidationError};
|
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
|
use std::env;
|
|
|
|
|
use std::fs;
|
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
|
use std::process::Command;
|
|
|
|
@@ -101,6 +102,14 @@ pub struct StatusResponse {
|
|
|
|
|
pub generated_config_path: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct SavedStateResponse {
|
|
|
|
|
pub profiles: Vec<ProfileDto>,
|
|
|
|
|
pub targets: Vec<TargetDto>,
|
|
|
|
|
pub generated_config_path: String,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
pub struct ProfileInputDto {
|
|
|
|
@@ -321,8 +330,20 @@ where
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn get_status(state: tauri::State<'_, CommandState>) -> Result<StatusResponse, CommandError> {
|
|
|
|
|
build_status(&state.storage())
|
|
|
|
|
pub async fn get_status(
|
|
|
|
|
state: tauri::State<'_, CommandState>,
|
|
|
|
|
) -> Result<StatusResponse, CommandError> {
|
|
|
|
|
let storage = state.storage();
|
|
|
|
|
tauri::async_runtime::spawn_blocking(move || build_status(&storage))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(background_task_error)?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn get_saved_state(
|
|
|
|
|
state: tauri::State<'_, CommandState>,
|
|
|
|
|
) -> Result<SavedStateResponse, CommandError> {
|
|
|
|
|
read_saved_state(&state.storage())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
@@ -354,10 +375,13 @@ pub fn save_target(
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn get_components(
|
|
|
|
|
pub async fn get_components(
|
|
|
|
|
state: tauri::State<'_, CommandState>,
|
|
|
|
|
) -> Result<Vec<ComponentStatusDto>, CommandError> {
|
|
|
|
|
read_components(&state.storage())
|
|
|
|
|
let storage = state.storage();
|
|
|
|
|
tauri::async_runtime::spawn_blocking(move || read_components(&storage))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(background_task_error)?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
@@ -389,7 +413,10 @@ pub fn get_logs(
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub fn open_config_location(state: tauri::State<'_, CommandState>) -> Result<String, CommandError> {
|
|
|
|
|
let storage = state.storage();
|
|
|
|
|
let generated_path = storage.paths().generated_dir.join("proxifyre-app-config.json");
|
|
|
|
|
let generated_path = storage
|
|
|
|
|
.paths()
|
|
|
|
|
.generated_dir
|
|
|
|
|
.join("proxifyre-app-config.json");
|
|
|
|
|
let config_path = detect_proxyfier_install()
|
|
|
|
|
.and_then(|detected| detected.config_path)
|
|
|
|
|
.filter(|path| path.exists())
|
|
|
|
@@ -403,6 +430,20 @@ pub fn open_config_location(state: tauri::State<'_, CommandState>) -> Result<Str
|
|
|
|
|
Ok(config_path.display().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn start_proxifyre_service() -> Result<ComponentStatusDto, CommandError> {
|
|
|
|
|
tauri::async_runtime::spawn_blocking(|| control_proxifyre_service(ServiceControlAction::Start))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(background_task_error)?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[tauri::command]
|
|
|
|
|
pub async fn stop_proxifyre_service() -> Result<ComponentStatusDto, CommandError> {
|
|
|
|
|
tauri::async_runtime::spawn_blocking(|| control_proxifyre_service(ServiceControlAction::Stop))
|
|
|
|
|
.await
|
|
|
|
|
.map_err(background_task_error)?
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn build_status(storage: &JsonStorage) -> Result<StatusResponse, CommandError> {
|
|
|
|
|
let profiles = storage.read_profiles().map_err(storage_error)?;
|
|
|
|
|
let targets = storage.read_targets().map_err(storage_error)?;
|
|
|
|
@@ -426,7 +467,11 @@ pub fn build_status(storage: &JsonStorage) -> Result<StatusResponse, CommandErro
|
|
|
|
|
routed_app_count,
|
|
|
|
|
active_target: active_target.map(TargetDto::from),
|
|
|
|
|
components: components.iter().map(ComponentStatusDto::from).collect(),
|
|
|
|
|
recent_activity: activity.iter().take(10).map(ActivityEntryDto::from).collect(),
|
|
|
|
|
recent_activity: activity
|
|
|
|
|
.iter()
|
|
|
|
|
.take(10)
|
|
|
|
|
.map(ActivityEntryDto::from)
|
|
|
|
|
.collect(),
|
|
|
|
|
generated_config_path: storage
|
|
|
|
|
.paths()
|
|
|
|
|
.generated_dir
|
|
|
|
@@ -501,6 +546,19 @@ pub fn read_activity(storage: &JsonStorage) -> Result<Vec<ActivityEntryDto>, Com
|
|
|
|
|
.map(|entries| entries.iter().map(ActivityEntryDto::from).collect())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn read_saved_state(storage: &JsonStorage) -> Result<SavedStateResponse, CommandError> {
|
|
|
|
|
Ok(SavedStateResponse {
|
|
|
|
|
profiles: read_profiles(storage)?,
|
|
|
|
|
targets: read_targets(storage)?,
|
|
|
|
|
generated_config_path: storage
|
|
|
|
|
.paths()
|
|
|
|
|
.generated_dir
|
|
|
|
|
.join("proxifyre-app-config.json")
|
|
|
|
|
.display()
|
|
|
|
|
.to_string(),
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
pub fn resolve_preview(
|
|
|
|
|
input: ProfileInputDto,
|
|
|
|
|
) -> Result<ResolveProfilePreviewResponse, CommandError> {
|
|
|
|
@@ -608,7 +666,10 @@ fn default_components() -> Vec<ComponentStatus> {
|
|
|
|
|
version: None,
|
|
|
|
|
path: None,
|
|
|
|
|
problems: Vec::new(),
|
|
|
|
|
actions: vec!["Открыть журнал".to_string(), "Скопировать диагностику".to_string()],
|
|
|
|
|
actions: vec![
|
|
|
|
|
"Открыть журнал".to_string(),
|
|
|
|
|
"Скопировать диагностику".to_string(),
|
|
|
|
|
],
|
|
|
|
|
},
|
|
|
|
|
ComponentStatus {
|
|
|
|
|
id: ComponentId::Proxyfier,
|
|
|
|
@@ -720,6 +781,386 @@ fn open_folder(path: &Path) -> Result<String, CommandError> {
|
|
|
|
|
Ok(path.display().to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
|
|
|
enum ServiceControlAction {
|
|
|
|
|
Start,
|
|
|
|
|
Stop,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
impl ServiceControlAction {
|
|
|
|
|
fn error_code(self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
ServiceControlAction::Start => "proxifyre_service_start_failed",
|
|
|
|
|
ServiceControlAction::Stop => "proxifyre_service_stop_failed",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn label(self) -> &'static str {
|
|
|
|
|
match self {
|
|
|
|
|
ServiceControlAction::Start => "запустить",
|
|
|
|
|
ServiceControlAction::Stop => "остановить",
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn control_proxifyre_service(
|
|
|
|
|
action: ServiceControlAction,
|
|
|
|
|
) -> Result<ComponentStatusDto, CommandError> {
|
|
|
|
|
let Some(detected) = detect_proxyfier_install() else {
|
|
|
|
|
return Err(CommandError::new(
|
|
|
|
|
"proxifyre_not_found",
|
|
|
|
|
"ProxiFyre не найден на компьютере.",
|
|
|
|
|
));
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
run_proxifyre_service_command(action, &service_name_candidates(&detected))?;
|
|
|
|
|
let refreshed = detect_proxyfier_install();
|
|
|
|
|
let component = proxyfier_component_from_detection(refreshed.as_ref());
|
|
|
|
|
|
|
|
|
|
Ok(ComponentStatusDto::from(&component))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn service_name_candidates(detected: &DetectedProxyfier) -> Vec<String> {
|
|
|
|
|
let mut names = Vec::new();
|
|
|
|
|
|
|
|
|
|
if let Some(service_name) = &detected.service_name {
|
|
|
|
|
names.push(service_name.clone());
|
|
|
|
|
}
|
|
|
|
|
for service_name in ["ProxiFyreService", "ProxiFyre"] {
|
|
|
|
|
if !names
|
|
|
|
|
.iter()
|
|
|
|
|
.any(|existing| existing.eq_ignore_ascii_case(service_name))
|
|
|
|
|
{
|
|
|
|
|
names.push(service_name.to_string());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
names
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
#[derive(Debug, Deserialize)]
|
|
|
|
|
#[serde(rename_all = "camelCase")]
|
|
|
|
|
struct ServiceCommandOutput {
|
|
|
|
|
success: bool,
|
|
|
|
|
code: String,
|
|
|
|
|
service_name: Option<String>,
|
|
|
|
|
status: Option<String>,
|
|
|
|
|
process_id: Option<u32>,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_proxifyre_service_command(
|
|
|
|
|
action: ServiceControlAction,
|
|
|
|
|
service_names: &[String],
|
|
|
|
|
) -> Result<(), CommandError> {
|
|
|
|
|
let names = service_names
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| format!("'{}'", escape_powershell_single(name)))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
|
|
|
|
let action_name = match action {
|
|
|
|
|
ServiceControlAction::Start => "start",
|
|
|
|
|
ServiceControlAction::Stop => "stop",
|
|
|
|
|
};
|
|
|
|
|
let script = format!(
|
|
|
|
|
r#"
|
|
|
|
|
$ErrorActionPreference = 'Stop'
|
|
|
|
|
$names = @({names})
|
|
|
|
|
$action = '{action_name}'
|
|
|
|
|
$service = $null
|
|
|
|
|
|
|
|
|
|
function Find-ProxiFyreService {{
|
|
|
|
|
foreach ($name in $names) {{
|
|
|
|
|
$candidate = Get-Service -Name $name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $candidate) {{ return $candidate }}
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
return Get-Service |
|
|
|
|
|
Where-Object {{ $_.Name -match 'ProxiFyre|Proxifyre' -or $_.DisplayName -match 'ProxiFyre|Proxifyre' }} |
|
|
|
|
|
Select-Object -First 1
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
function Get-ServiceProcessId([string]$name) {{
|
|
|
|
|
$escapedName = $name.Replace("'", "''")
|
|
|
|
|
$record = Get-CimInstance Win32_Service -Filter "Name='$escapedName'" -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -eq $record) {{ return 0 }}
|
|
|
|
|
return [int]$record.ProcessId
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
function Get-ServiceStatus([string]$name) {{
|
|
|
|
|
$current = Get-Service -Name $name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -eq $current) {{ return $null }}
|
|
|
|
|
return $current.Status.ToString()
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
function Write-ServiceResult([bool]$success, [string]$code, [string]$status, [int]$processId) {{
|
|
|
|
|
[PSCustomObject]@{{
|
|
|
|
|
success = $success
|
|
|
|
|
code = $code
|
|
|
|
|
serviceName = if ($null -ne $service) {{ $service.Name }} else {{ $null }}
|
|
|
|
|
status = $status
|
|
|
|
|
processId = $processId
|
|
|
|
|
}} | ConvertTo-Json -Compress
|
|
|
|
|
exit 0
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$service = Find-ProxiFyreService
|
|
|
|
|
if ($null -eq $service) {{
|
|
|
|
|
Write-ServiceResult $false 'service_not_found' $null 0
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$status = $service.Status.ToString()
|
|
|
|
|
$processId = Get-ServiceProcessId $service.Name
|
|
|
|
|
|
|
|
|
|
if ($action -eq 'start') {{
|
|
|
|
|
if ($status -eq 'Running') {{
|
|
|
|
|
Write-ServiceResult $true 'already_running' $status $processId
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
try {{
|
|
|
|
|
Start-Service -Name $service.Name -ErrorAction Stop
|
|
|
|
|
$service = Get-Service -Name $service.Name
|
|
|
|
|
$service.WaitForStatus('Running', [TimeSpan]::FromSeconds(15))
|
|
|
|
|
}} catch {{
|
|
|
|
|
Write-ServiceResult $false 'start_failed' (Get-ServiceStatus $service.Name) (Get-ServiceProcessId $service.Name)
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
Write-ServiceResult ($service.Status -eq 'Running') 'started' $service.Status.ToString() (Get-ServiceProcessId $service.Name)
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
if ($status -eq 'Stopped') {{
|
|
|
|
|
Write-ServiceResult $true 'already_stopped' $status $processId
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
try {{
|
|
|
|
|
if ($service.CanStop) {{
|
|
|
|
|
Stop-Service -Name $service.Name -Force -ErrorAction Stop
|
|
|
|
|
}}
|
|
|
|
|
}} catch {{}}
|
|
|
|
|
|
|
|
|
|
try {{
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service -and $service.Status -ne 'Stopped') {{
|
|
|
|
|
$null = & sc.exe stop $service.Name 2>$null
|
|
|
|
|
}}
|
|
|
|
|
}} catch {{}}
|
|
|
|
|
|
|
|
|
|
try {{
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service -and $service.Status -ne 'Stopped') {{
|
|
|
|
|
$service.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(8))
|
|
|
|
|
}}
|
|
|
|
|
}} catch {{}}
|
|
|
|
|
|
|
|
|
|
$status = Get-ServiceStatus $service.Name
|
|
|
|
|
$processId = Get-ServiceProcessId $service.Name
|
|
|
|
|
if ($status -ne 'Stopped' -and $processId -gt 0) {{
|
|
|
|
|
try {{
|
|
|
|
|
$null = & taskkill.exe /PID $processId /F 2>$null
|
|
|
|
|
Start-Sleep -Milliseconds 700
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service) {{
|
|
|
|
|
$service.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(8))
|
|
|
|
|
}}
|
|
|
|
|
}} catch {{}}
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$status = Get-ServiceStatus $service.Name
|
|
|
|
|
$processId = Get-ServiceProcessId $service.Name
|
|
|
|
|
if ($status -eq 'Stopped') {{
|
|
|
|
|
Write-ServiceResult $true 'stopped' $status $processId
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
Write-ServiceResult $false 'stop_failed' $status $processId
|
|
|
|
|
"#
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
let output = Command::new("powershell")
|
|
|
|
|
.args([
|
|
|
|
|
"-NoProfile",
|
|
|
|
|
"-NonInteractive",
|
|
|
|
|
"-ExecutionPolicy",
|
|
|
|
|
"Bypass",
|
|
|
|
|
"-Command",
|
|
|
|
|
script.as_str(),
|
|
|
|
|
])
|
|
|
|
|
.output()
|
|
|
|
|
.map_err(|error| {
|
|
|
|
|
CommandError::new(
|
|
|
|
|
action.error_code(),
|
|
|
|
|
format!("Не удалось {} службу ProxiFyre: {error}", action.label()),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
let result = parse_service_command_output(&output.stdout).ok_or_else(|| {
|
|
|
|
|
CommandError::new(
|
|
|
|
|
action.error_code(),
|
|
|
|
|
service_script_failed_message(action, output.status.code()),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
if result.success {
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if matches!(result.code.as_str(), "start_failed" | "stop_failed") {
|
|
|
|
|
run_elevated_proxifyre_service_command(action, service_names, &result)?;
|
|
|
|
|
return Ok(());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Err(CommandError::new(
|
|
|
|
|
action.error_code(),
|
|
|
|
|
service_command_failed_message(action, &result),
|
|
|
|
|
))
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn run_elevated_proxifyre_service_command(
|
|
|
|
|
action: ServiceControlAction,
|
|
|
|
|
service_names: &[String],
|
|
|
|
|
direct_result: &ServiceCommandOutput,
|
|
|
|
|
) -> Result<(), CommandError> {
|
|
|
|
|
let script_path = write_elevated_service_script(action, service_names)?;
|
|
|
|
|
let launch_script = format!(
|
|
|
|
|
"$p = Start-Process -FilePath 'powershell.exe' -Verb RunAs -Wait -PassThru -WindowStyle Hidden -ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','{}'); exit $p.ExitCode",
|
|
|
|
|
escape_powershell_single(&script_path.display().to_string())
|
|
|
|
|
);
|
|
|
|
|
let output = Command::new("powershell")
|
|
|
|
|
.args([
|
|
|
|
|
"-NoProfile",
|
|
|
|
|
"-NonInteractive",
|
|
|
|
|
"-ExecutionPolicy",
|
|
|
|
|
"Bypass",
|
|
|
|
|
"-Command",
|
|
|
|
|
launch_script.as_str(),
|
|
|
|
|
])
|
|
|
|
|
.output();
|
|
|
|
|
|
|
|
|
|
let _ = fs::remove_file(&script_path);
|
|
|
|
|
|
|
|
|
|
match output {
|
|
|
|
|
Ok(output) if output.status.success() => Ok(()),
|
|
|
|
|
Ok(output) => Err(CommandError::new(
|
|
|
|
|
action.error_code(),
|
|
|
|
|
elevated_service_failed_message(action, direct_result, output.status.code()),
|
|
|
|
|
)),
|
|
|
|
|
Err(error) => Err(CommandError::new(
|
|
|
|
|
action.error_code(),
|
|
|
|
|
format!(
|
|
|
|
|
"Не удалось запросить права администратора, чтобы {} службу ProxiFyre: {error}",
|
|
|
|
|
action.label()
|
|
|
|
|
),
|
|
|
|
|
)),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn write_elevated_service_script(
|
|
|
|
|
action: ServiceControlAction,
|
|
|
|
|
service_names: &[String],
|
|
|
|
|
) -> Result<PathBuf, CommandError> {
|
|
|
|
|
let nonce = SystemTime::now()
|
|
|
|
|
.duration_since(UNIX_EPOCH)
|
|
|
|
|
.map(|duration| duration.as_millis())
|
|
|
|
|
.unwrap_or(0);
|
|
|
|
|
let script_path = env::temp_dir().join(format!("vpn-proxy-proxifyre-service-{nonce}.ps1"));
|
|
|
|
|
let script = elevated_service_script(action, service_names);
|
|
|
|
|
|
|
|
|
|
fs::write(&script_path, script).map_err(|error| {
|
|
|
|
|
CommandError::new(
|
|
|
|
|
action.error_code(),
|
|
|
|
|
format!(
|
|
|
|
|
"Не удалось подготовить временный скрипт для управления ProxiFyre '{}': {error}",
|
|
|
|
|
script_path.display()
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
})?;
|
|
|
|
|
|
|
|
|
|
Ok(script_path)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn elevated_service_script(action: ServiceControlAction, service_names: &[String]) -> String {
|
|
|
|
|
let names = service_names
|
|
|
|
|
.iter()
|
|
|
|
|
.map(|name| format!("'{}'", escape_powershell_single(name)))
|
|
|
|
|
.collect::<Vec<_>>()
|
|
|
|
|
.join(", ");
|
|
|
|
|
let action_name = match action {
|
|
|
|
|
ServiceControlAction::Start => "start",
|
|
|
|
|
ServiceControlAction::Stop => "stop",
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
format!(
|
|
|
|
|
r#"
|
|
|
|
|
$ErrorActionPreference = 'SilentlyContinue'
|
|
|
|
|
$names = @({names})
|
|
|
|
|
$action = '{action_name}'
|
|
|
|
|
$service = $null
|
|
|
|
|
|
|
|
|
|
foreach ($name in $names) {{
|
|
|
|
|
$service = Get-Service -Name $name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service) {{ break }}
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
if ($null -eq $service) {{
|
|
|
|
|
$service = Get-Service |
|
|
|
|
|
Where-Object {{ $_.Name -match 'ProxiFyre|Proxifyre' -or $_.DisplayName -match 'ProxiFyre|Proxifyre' }} |
|
|
|
|
|
Select-Object -First 1
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
if ($null -eq $service) {{ exit 2 }}
|
|
|
|
|
|
|
|
|
|
function Get-ServiceProcessId([string]$name) {{
|
|
|
|
|
$escapedName = $name.Replace("'", "''")
|
|
|
|
|
$record = Get-CimInstance Win32_Service -Filter "Name='$escapedName'" -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -eq $record) {{ return 0 }}
|
|
|
|
|
return [int]$record.ProcessId
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
if ($action -eq 'start') {{
|
|
|
|
|
if ($service.Status -eq 'Running') {{ exit 0 }}
|
|
|
|
|
Start-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service) {{
|
|
|
|
|
try {{ $service.WaitForStatus('Running', [TimeSpan]::FromSeconds(15)) }} catch {{}}
|
|
|
|
|
if ($service.Status -eq 'Running') {{ exit 0 }}
|
|
|
|
|
}}
|
|
|
|
|
exit 3
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
if ($service.Status -eq 'Stopped') {{ exit 0 }}
|
|
|
|
|
|
|
|
|
|
if ($service.CanStop) {{
|
|
|
|
|
Stop-Service -Name $service.Name -Force -ErrorAction SilentlyContinue
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service -and $service.Status -ne 'Stopped') {{
|
|
|
|
|
$null = & sc.exe stop $service.Name 2>$null
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service -and $service.Status -ne 'Stopped') {{
|
|
|
|
|
try {{ $service.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(8)) }} catch {{}}
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service -and $service.Status -ne 'Stopped') {{
|
|
|
|
|
$processId = Get-ServiceProcessId $service.Name
|
|
|
|
|
if ($processId -gt 0) {{
|
|
|
|
|
$null = & taskkill.exe /PID $processId /F 2>$null
|
|
|
|
|
Start-Sleep -Milliseconds 700
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -ne $service) {{
|
|
|
|
|
try {{ $service.WaitForStatus('Stopped', [TimeSpan]::FromSeconds(8)) }} catch {{}}
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
}}
|
|
|
|
|
|
|
|
|
|
$service = Get-Service -Name $service.Name -ErrorAction SilentlyContinue
|
|
|
|
|
if ($null -eq $service -or $service.Status -eq 'Stopped') {{ exit 0 }}
|
|
|
|
|
exit 4
|
|
|
|
|
"#
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn apply_to_detected_proxyfier(
|
|
|
|
|
request: HelperApplyRequest<'_>,
|
|
|
|
|
detected: &DetectedProxyfier,
|
|
|
|
@@ -832,6 +1273,104 @@ fn storage_error(error: std::io::Error) -> CommandError {
|
|
|
|
|
CommandError::new("storage_error", error.to_string())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn background_task_error(error: impl std::fmt::Display) -> CommandError {
|
|
|
|
|
CommandError::new(
|
|
|
|
|
"background_task_failed",
|
|
|
|
|
format!("Фоновая проверка не завершилась: {error}"),
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn parse_service_command_output(stdout: &[u8]) -> Option<ServiceCommandOutput> {
|
|
|
|
|
let stdout = String::from_utf8_lossy(stdout);
|
|
|
|
|
let payload = stdout
|
|
|
|
|
.lines()
|
|
|
|
|
.rev()
|
|
|
|
|
.map(str::trim)
|
|
|
|
|
.find(|line| line.starts_with('{') && line.ends_with('}'))?;
|
|
|
|
|
|
|
|
|
|
serde_json::from_str(payload).ok()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn service_script_failed_message(action: ServiceControlAction, exit_code: Option<i32>) -> String {
|
|
|
|
|
let exit_code = exit_code
|
|
|
|
|
.map(|code| format!(" Код выхода PowerShell: {code}."))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
format!(
|
|
|
|
|
"Не удалось {} службу ProxiFyre: команда управления службой не вернула корректный результат.{exit_code}",
|
|
|
|
|
action.label()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn service_command_failed_message(
|
|
|
|
|
action: ServiceControlAction,
|
|
|
|
|
result: &ServiceCommandOutput,
|
|
|
|
|
) -> String {
|
|
|
|
|
let service_name = result
|
|
|
|
|
.service_name
|
|
|
|
|
.as_deref()
|
|
|
|
|
.filter(|value| !value.trim().is_empty())
|
|
|
|
|
.unwrap_or("ProxiFyre");
|
|
|
|
|
let status = result
|
|
|
|
|
.status
|
|
|
|
|
.as_deref()
|
|
|
|
|
.filter(|value| !value.trim().is_empty())
|
|
|
|
|
.unwrap_or("неизвестен");
|
|
|
|
|
let pid = result
|
|
|
|
|
.process_id
|
|
|
|
|
.filter(|value| *value > 0)
|
|
|
|
|
.map(|value| format!(", PID: {value}"))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
match result.code.as_str() {
|
|
|
|
|
"service_not_found" => "Служба ProxiFyre не найдена.".to_string(),
|
|
|
|
|
"start_failed" => format!(
|
|
|
|
|
"Не удалось запустить службу {service_name}. Текущий статус: {status}{pid}. Попробуй запустить приложение от имени администратора."
|
|
|
|
|
),
|
|
|
|
|
"stop_failed" => format!(
|
|
|
|
|
"Не удалось остановить службу {service_name} даже после принудительной попытки. Текущий статус: {status}{pid}. Запусти приложение от имени администратора или останови службу вручную в services.msc."
|
|
|
|
|
),
|
|
|
|
|
_ => format!(
|
|
|
|
|
"Не удалось {} службу {service_name}. Текущий статус: {status}{pid}.",
|
|
|
|
|
action.label()
|
|
|
|
|
),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn elevated_service_failed_message(
|
|
|
|
|
action: ServiceControlAction,
|
|
|
|
|
direct_result: &ServiceCommandOutput,
|
|
|
|
|
exit_code: Option<i32>,
|
|
|
|
|
) -> String {
|
|
|
|
|
let service_name = direct_result
|
|
|
|
|
.service_name
|
|
|
|
|
.as_deref()
|
|
|
|
|
.filter(|value| !value.trim().is_empty())
|
|
|
|
|
.unwrap_or("ProxiFyre");
|
|
|
|
|
let status = direct_result
|
|
|
|
|
.status
|
|
|
|
|
.as_deref()
|
|
|
|
|
.filter(|value| !value.trim().is_empty())
|
|
|
|
|
.unwrap_or("неизвестен");
|
|
|
|
|
let pid = direct_result
|
|
|
|
|
.process_id
|
|
|
|
|
.filter(|value| *value > 0)
|
|
|
|
|
.map(|value| format!(", PID: {value}"))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
let exit_code = exit_code
|
|
|
|
|
.map(|code| format!(" Код elevated-команды: {code}."))
|
|
|
|
|
.unwrap_or_default();
|
|
|
|
|
|
|
|
|
|
format!(
|
|
|
|
|
"Не удалось {} службу {service_name} даже после запроса прав администратора. До запроса UAC статус был: {status}{pid}.{exit_code} Если появлялось окно UAC, проверь, что оно было подтверждено.",
|
|
|
|
|
action.label()
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn escape_powershell_single(value: &str) -> String {
|
|
|
|
|
value.replace('\'', "''")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn validation_error(errors: Vec<ValidationError>) -> CommandError {
|
|
|
|
|
CommandError::with_details(
|
|
|
|
|
"validation_error",
|
|
|
|
|