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
This commit is contained in:
251
ostp-setup/src/service.rs
Normal file
251
ostp-setup/src/service.rs
Normal file
@@ -0,0 +1,251 @@
|
||||
//! 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
|
||||
}
|
||||
Reference in New Issue
Block a user