Files
ospab.network/ostp-setup/src/service.rs
ospab 7e1c87e70b feat: Windows Setup Wizard (ostp-setup) with Tauri
- 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
2026-01-01 21:49:37 +03:00

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
}