- Tauri 2.0 based graphical installer - Access Key parsing with AES-256-GCM encryption - Windows Service installation via sc.exe - WinTUN driver extraction from embedded resources - System requirements checking (admin, AES-NI, OS version) - Modern dark UI with step-by-step wizard flow - Country/region selection for SNI mimicry
252 lines
6.8 KiB
Rust
252 lines
6.8 KiB
Rust
//! Windows Service management for OSTP Client
|
|
//!
|
|
//! Installs, configures, and manages the OSTP client as a Windows Service.
|
|
//! Uses sc.exe for broader compatibility.
|
|
|
|
use anyhow::{Context, Result};
|
|
use std::path::PathBuf;
|
|
use std::process::Command;
|
|
|
|
/// Service name
|
|
const SERVICE_NAME: &str = "OstpClient";
|
|
|
|
/// Service display name
|
|
const SERVICE_DISPLAY_NAME: &str = "Ospab Network OSTP Client";
|
|
|
|
/// Service description
|
|
const SERVICE_DESCRIPTION: &str =
|
|
"Provides secure, stealth VPN connectivity through the OSTP protocol.";
|
|
|
|
/// Install OSTP client as a Windows Service using sc.exe
|
|
pub async fn install_service(install_path: &PathBuf) -> Result<()> {
|
|
#[cfg(windows)]
|
|
{
|
|
let executable_path = install_path.join("ostp-client.exe");
|
|
|
|
if !executable_path.exists() {
|
|
anyhow::bail!("ostp-client.exe not found at {:?}", executable_path);
|
|
}
|
|
|
|
let config_path = install_path.join("ostp-client.json");
|
|
let bin_path = format!(
|
|
"\"{}\" --service --config \"{}\"",
|
|
executable_path.display(),
|
|
config_path.display()
|
|
);
|
|
|
|
// Create service
|
|
let output = Command::new("sc.exe")
|
|
.args([
|
|
"create",
|
|
SERVICE_NAME,
|
|
&format!("binPath= {}", bin_path),
|
|
&format!("DisplayName= {}", SERVICE_DISPLAY_NAME),
|
|
"start= auto",
|
|
"depend= Tcpip/Dnscache",
|
|
])
|
|
.output()
|
|
.context("Failed to run sc.exe create")?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
if !stderr.contains("1073") { // Service already exists
|
|
anyhow::bail!("Failed to create service: {}", stderr);
|
|
}
|
|
}
|
|
|
|
// Set description
|
|
let _ = Command::new("sc.exe")
|
|
.args(["description", SERVICE_NAME, SERVICE_DESCRIPTION])
|
|
.output();
|
|
|
|
// Configure recovery options (restart on failure)
|
|
let _ = Command::new("sc.exe")
|
|
.args([
|
|
"failure",
|
|
SERVICE_NAME,
|
|
"reset= 86400",
|
|
"actions= restart/60000/restart/120000/restart/300000",
|
|
])
|
|
.output();
|
|
|
|
tracing::info!("Service {} installed successfully", SERVICE_NAME);
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
{
|
|
let _ = install_path;
|
|
anyhow::bail!("Windows Service installation is only supported on Windows");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Start the OSTP service
|
|
pub async fn start_service() -> Result<()> {
|
|
#[cfg(windows)]
|
|
{
|
|
let output = Command::new("sc.exe")
|
|
.args(["start", SERVICE_NAME])
|
|
.output()
|
|
.context("Failed to run sc.exe start")?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
if !stderr.contains("1056") { // Service already running
|
|
anyhow::bail!("Failed to start service: {}", stderr);
|
|
}
|
|
}
|
|
|
|
tracing::info!("Service {} started", SERVICE_NAME);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Stop the OSTP service
|
|
pub async fn stop_service() -> Result<()> {
|
|
#[cfg(windows)]
|
|
{
|
|
let output = Command::new("sc.exe")
|
|
.args(["stop", SERVICE_NAME])
|
|
.output()
|
|
.context("Failed to run sc.exe stop")?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
if !stderr.contains("1062") { // Service not running
|
|
anyhow::bail!("Failed to stop service: {}", stderr);
|
|
}
|
|
}
|
|
|
|
// Wait for service to stop
|
|
for _ in 0..30 {
|
|
tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
|
if !is_running() {
|
|
break;
|
|
}
|
|
}
|
|
|
|
tracing::info!("Service {} stopped", SERVICE_NAME);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Uninstall the OSTP service
|
|
pub async fn uninstall_service() -> Result<()> {
|
|
// Stop the service first
|
|
stop_service().await.ok();
|
|
|
|
#[cfg(windows)]
|
|
{
|
|
let output = Command::new("sc.exe")
|
|
.args(["delete", SERVICE_NAME])
|
|
.output()
|
|
.context("Failed to run sc.exe delete")?;
|
|
|
|
if !output.status.success() {
|
|
let stderr = String::from_utf8_lossy(&output.stderr);
|
|
if !stderr.contains("1060") { // Service doesn't exist
|
|
anyhow::bail!("Failed to delete service: {}", stderr);
|
|
}
|
|
}
|
|
|
|
tracing::info!("Service {} uninstalled", SERVICE_NAME);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Enable service to start at system startup
|
|
pub async fn enable_autostart() -> Result<()> {
|
|
#[cfg(windows)]
|
|
{
|
|
Command::new("sc.exe")
|
|
.args(["config", SERVICE_NAME, "start= auto"])
|
|
.output()
|
|
.context("Failed to enable autostart")?;
|
|
|
|
tracing::info!("Autostart enabled for {}", SERVICE_NAME);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Disable service autostart
|
|
#[allow(dead_code)]
|
|
pub async fn disable_autostart() -> Result<()> {
|
|
#[cfg(windows)]
|
|
{
|
|
Command::new("sc.exe")
|
|
.args(["config", SERVICE_NAME, "start= demand"])
|
|
.output()
|
|
.context("Failed to disable autostart")?;
|
|
|
|
tracing::info!("Autostart disabled for {}", SERVICE_NAME);
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
/// Check if service is installed
|
|
#[allow(dead_code)]
|
|
pub fn is_installed() -> bool {
|
|
#[cfg(windows)]
|
|
{
|
|
Command::new("sc.exe")
|
|
.args(["query", SERVICE_NAME])
|
|
.output()
|
|
.map(|o| o.status.success())
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
false
|
|
}
|
|
|
|
/// Check if service is running
|
|
pub fn is_running() -> bool {
|
|
#[cfg(windows)]
|
|
{
|
|
Command::new("sc.exe")
|
|
.args(["query", SERVICE_NAME])
|
|
.output()
|
|
.map(|o| {
|
|
let stdout = String::from_utf8_lossy(&o.stdout);
|
|
stdout.contains("RUNNING")
|
|
})
|
|
.unwrap_or(false)
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
false
|
|
}
|
|
|
|
/// Get service status
|
|
#[allow(dead_code)]
|
|
pub fn get_status() -> Option<String> {
|
|
#[cfg(windows)]
|
|
{
|
|
Command::new("sc.exe")
|
|
.args(["query", SERVICE_NAME])
|
|
.output()
|
|
.ok()
|
|
.and_then(|o| {
|
|
let stdout = String::from_utf8_lossy(&o.stdout);
|
|
if stdout.contains("RUNNING") {
|
|
Some("Running".to_string())
|
|
} else if stdout.contains("STOPPED") {
|
|
Some("Stopped".to_string())
|
|
} else if stdout.contains("PENDING") {
|
|
Some("Pending".to_string())
|
|
} else {
|
|
Some("Unknown".to_string())
|
|
}
|
|
})
|
|
}
|
|
|
|
#[cfg(not(windows))]
|
|
None
|
|
}
|