feat: Windows stack (daemon, installer, GUI)

Components:
- ostp-daemon: Windows Service with Named Pipe IPC
- ostp-installer: Setup wizard with admin privileges
- ostp-gui: Tauri dark theme UI (450x600)

Features:
- Background service management (OspabGuard)
- IPC commands: CONNECT/DISCONNECT/STATUS
- Firewall rules auto-configuration
- Wintun driver placeholder (download from wintun.net)
- Real-time stats display (upload/download/ping)

Note: Requires wintun.dll download for full functionality
This commit is contained in:
2026-01-02 02:17:15 +03:00
parent 7ed4217987
commit 85a2b01074
40 changed files with 1460 additions and 7376 deletions

39
ostp-gui/src/ipc.rs Normal file
View File

@@ -0,0 +1,39 @@
//! IPC communication with ostp-daemon via Named Pipe
use anyhow::{Context, Result};
use std::io::{Read, Write};
const PIPE_NAME: &str = r"\\.\pipe\ostp-daemon";
pub async fn send_command(command: &str) -> Result<String> {
#[cfg(windows)]
{
use std::fs::OpenOptions;
use std::os::windows::fs::OpenOptionsExt;
use winapi::um::winbase::FILE_FLAG_OVERLAPPED;
// Connect to Named Pipe
let mut pipe = OpenOptions::new()
.read(true)
.write(true)
.custom_flags(FILE_FLAG_OVERLAPPED)
.open(PIPE_NAME)
.context("Failed to connect to ostp-daemon. Is the service running?")?;
// Send command
pipe.write_all(command.as_bytes())?;
pipe.write_all(b"\n")?;
pipe.flush()?;
// Read response
let mut response = String::new();
pipe.read_to_string(&mut response)?;
Ok(response.trim().to_string())
}
#[cfg(not(windows))]
{
anyhow::bail!("Named pipes are only supported on Windows");
}
}

56
ostp-gui/src/main.rs Normal file
View File

@@ -0,0 +1,56 @@
//! OSTP GUI - Windows Client Interface
//!
//! Communicates with ostp-daemon via Named Pipe
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
mod ipc;
mod state;
use tauri::Manager;
use state::AppState;
#[tauri::command]
async fn connect_vpn(state: tauri::State<'_, AppState>) -> Result<String, String> {
ipc::send_command("CONNECT")
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn disconnect_vpn(state: tauri::State<'_, AppState>) -> Result<String, String> {
ipc::send_command("DISCONNECT")
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn get_status(state: tauri::State<'_, AppState>) -> Result<String, String> {
ipc::send_command("STATUS")
.await
.map_err(|e| e.to_string())
}
#[tauri::command]
async fn fetch_servers() -> Result<Vec<String>, String> {
// TODO: Fetch from Master Node API
Ok(vec![
"RU - Moscow".to_string(),
"US - New York".to_string(),
"DE - Frankfurt".to_string(),
])
}
fn main() {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.manage(AppState::new())
.invoke_handler(tauri::generate_handler![
connect_vpn,
disconnect_vpn,
get_status,
fetch_servers
])
.run(tauri::generate_context!())
.expect("error while running tauri application");
}

37
ostp-gui/src/state.rs Normal file
View File

@@ -0,0 +1,37 @@
//! Application state management
use std::sync::{Arc, Mutex};
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ConnectionStatus {
pub connected: bool,
pub server: Option<String>,
pub upload_speed: u64,
pub download_speed: u64,
pub ping: u32,
}
impl Default for ConnectionStatus {
fn default() -> Self {
Self {
connected: false,
server: None,
upload_speed: 0,
download_speed: 0,
ping: 0,
}
}
}
pub struct AppState {
pub status: Arc<Mutex<ConnectionStatus>>,
}
impl AppState {
pub fn new() -> Self {
Self {
status: Arc::new(Mutex::new(ConnectionStatus::default())),
}
}
}