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

32
ostp-installer/Cargo.toml Normal file
View File

@@ -0,0 +1,32 @@
[package]
name = "ostp-installer"
version.workspace = true
edition.workspace = true
description = "OSTP Windows Installer - Setup Wizard"
[[bin]]
name = "ostp-installer"
path = "src/main.rs"
[dependencies]
anyhow.workspace = true
tracing.workspace = true
tracing-subscriber.workspace = true
# Windows-specific
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = [
"winuser", "winsvc", "winbase", "processthreadsapi", "handleapi", "errhandlingapi",
"securitybaseapi", "winnt"
] }
windows = { version = "0.58", features = [
"Win32_System_Services",
"Win32_Security",
"Win32_Foundation",
"Win32_Storage_FileSystem",
"Win32_System_Registry",
] }
# GUI for wizard
native-windows-gui = "1.0"
native-windows-derive = "1.0"

View File

@@ -0,0 +1,12 @@
# Wintun DLL Placeholder
**NOTE**: This is a placeholder file. You need to download the actual `wintun.dll` from:
https://www.wintun.net/
**Instructions:**
1. Download Wintun from https://www.wintun.net/
2. Extract the archive
3. Copy `wintun/bin/amd64/wintun.dll` to this directory
4. The installer will embed this DLL at compile time using `include_bytes!`
**License**: Wintun is dual-licensed under GPLv2 and a commercial license.

View File

@@ -0,0 +1,3 @@
// Placeholder wintun.dll
// Download actual file from https://www.wintun.net/
// This file is here to allow compilation without the actual DLL

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
<assemblyIdentity
version="1.0.0.0"
processorArchitecture="amd64"
name="OspabInstaller"
type="win32"
/>
<description>OSTP Installer</description>
<trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
<security>
<requestedPrivileges>
<requestedExecutionLevel level="requireAdministrator" uiAccess="false"/>
</requestedPrivileges>
</security>
</trustInfo>
</assembly>

View File

@@ -0,0 +1,63 @@
//! Windows Firewall configuration
use anyhow::Result;
use std::process::Command;
pub fn add_firewall_rules() -> Result<()> {
let daemon_path = crate::get_install_path().join("ostp-daemon.exe");
let gui_path = crate::get_install_path().join("ostp-gui.exe");
// Add inbound rule for daemon
add_rule(
"OSTP Daemon Inbound",
&daemon_path.to_string_lossy(),
"in",
)?;
// Add outbound rule for daemon
add_rule(
"OSTP Daemon Outbound",
&daemon_path.to_string_lossy(),
"out",
)?;
// Add inbound rule for GUI
add_rule(
"OSTP GUI Inbound",
&gui_path.to_string_lossy(),
"in",
)?;
// Add outbound rule for GUI
add_rule(
"OSTP GUI Outbound",
&gui_path.to_string_lossy(),
"out",
)?;
tracing::info!("Firewall rules added successfully");
Ok(())
}
fn add_rule(name: &str, program: &str, direction: &str) -> Result<()> {
let output = Command::new("netsh")
.args([
"advfirewall",
"firewall",
"add",
"rule",
&format!("name={}", name),
&format!("dir={}", direction),
"action=allow",
&format!("program={}", program),
"enable=yes",
])
.output()?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
anyhow::bail!("Failed to add firewall rule '{}': {}", name, stderr);
}
Ok(())
}

View File

@@ -0,0 +1,88 @@
//! OSTP Windows Installer - Setup Wizard
//!
//! Installs:
//! - Wintun driver
//! - OspabGuard Windows Service
//! - Firewall rules
//! - GUI shortcuts
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
use anyhow::{Context, Result};
use std::path::PathBuf;
mod wizard;
mod wintun;
mod service;
mod firewall;
// Placeholder - download actual wintun.dll from https://www.wintun.net/
// const WINTUN_DLL: &[u8] = include_bytes!("../../assets/wintun.dll");
fn main() -> Result<()> {
// Check for admin privileges
if !is_elevated() {
anyhow::bail!("Installer must be run as Administrator");
}
// Initialize logging
tracing_subscriber::fmt()
.with_max_level(tracing::Level::INFO)
.init();
tracing::info!("OSTP Installer starting");
// Show wizard UI
wizard::run_wizard()?;
Ok(())
}
#[cfg(windows)]
fn is_elevated() -> bool {
use winapi::um::securitybaseapi::*;
use winapi::um::processthreadsapi::*;
use winapi::um::winnt::*;
use std::mem;
use std::ptr;
unsafe {
let mut handle: HANDLE = ptr::null_mut();
if OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &mut handle) == 0 {
return false;
}
let mut elevation: TOKEN_ELEVATION = mem::zeroed();
let mut size = mem::size_of::<TOKEN_ELEVATION>() as u32;
if GetTokenInformation(
handle,
TokenElevation,
&mut elevation as *mut _ as *mut _,
size,
&mut size,
) == 0
{
return false;
}
elevation.TokenIsElevated != 0
}
}
pub fn get_install_path() -> PathBuf {
PathBuf::from(r"C:\Program Files\Ospab\OSTP")
}
pub fn install_wintun_dll() -> Result<()> {
let install_path = get_install_path();
std::fs::create_dir_all(&install_path)?;
// TODO: Copy wintun.dll when available
// let wintun_path = install_path.join("wintun.dll");
// std::fs::write(&wintun_path, WINTUN_DLL)
// .context("Failed to write wintun.dll")?;
tracing::info!("Wintun DLL installation placeholder");
Ok(())
}

View File

@@ -0,0 +1,80 @@
//! Windows Service registration
use anyhow::{Context, Result};
use std::ffi::OsString;
use std::os::windows::ffi::OsStrExt;
use windows::Win32::System::Services::*;
use windows::Win32::Foundation::*;
use windows::core::PCWSTR;
pub fn install_service() -> Result<()> {
let service_name = "OspabGuard";
let display_name = "Ospab OSTP VPN Service";
let description = "Manages secure OSTP VPN tunnel connections";
let daemon_path = crate::get_install_path().join("ostp-daemon.exe");
unsafe {
// Open Service Control Manager
let scm = OpenSCManagerW(
PCWSTR::null(),
PCWSTR::null(),
SC_MANAGER_CREATE_SERVICE,
)?;
if scm.is_invalid() {
anyhow::bail!("Failed to open Service Control Manager");
}
// Create service
let service_name_wide = to_wide_string(service_name);
let display_name_wide = to_wide_string(display_name);
let binary_path_wide = to_wide_string(&daemon_path.to_string_lossy());
let service = CreateServiceW(
scm,
PCWSTR(service_name_wide.as_ptr()),
PCWSTR(display_name_wide.as_ptr()),
SERVICE_ALL_ACCESS,
SERVICE_WIN32_OWN_PROCESS,
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL,
PCWSTR(binary_path_wide.as_ptr()),
PCWSTR::null(),
None,
PCWSTR::null(),
PCWSTR::null(),
PCWSTR::null(),
)?;
if service.is_invalid() {
CloseServiceHandle(scm)?;
anyhow::bail!("Failed to create service");
}
// Set description
let description_wide = to_wide_string(description);
let mut service_desc = SERVICE_DESCRIPTIONW {
lpDescription: PWSTR(description_wide.as_ptr() as *mut _),
};
ChangeServiceConfig2W(
service,
SERVICE_CONFIG_DESCRIPTION,
Some(&service_desc as *const _ as *const _),
)?;
CloseServiceHandle(service)?;
CloseServiceHandle(scm)?;
}
tracing::info!("Service '{}' installed successfully", service_name);
Ok(())
}
fn to_wide_string(s: &str) -> Vec<u16> {
OsString::from(s)
.encode_wide()
.chain(std::iter::once(0))
.collect()
}

View File

@@ -0,0 +1,10 @@
//! Wintun driver installation
use anyhow::Result;
pub fn install_wintun_driver() -> Result<()> {
// Wintun doesn't require manual driver installation
// The DLL handles driver installation automatically on first use
tracing::info!("Wintun DLL will auto-install driver on first use");
Ok(())
}

View File

@@ -0,0 +1,100 @@
//! Wizard UI for installation process
use anyhow::Result;
use native_windows_gui as nwg;
use native_windows_derive as nwd;
use crate::{install_wintun_dll, service, firewall};
#[derive(Default, nwd::NwgUi)]
pub struct InstallerWizard {
#[nwg_control(size: (600, 400), position: (300, 300), title: "OSTP Installer", flags: "WINDOW|VISIBLE")]
#[nwg_events(OnWindowClose: [InstallerWizard::exit])]
window: nwg::Window,
#[nwg_layout(parent: window, spacing: 1)]
grid: nwg::GridLayout,
#[nwg_control(text: "Welcome to OSTP Installer", font: Some(&data.title_font))]
#[nwg_layout_item(layout: grid, row: 0, col: 0, col_span: 2)]
title: nwg::Label,
#[nwg_control(text: "This wizard will install OSTP VPN on your system.\n\nClick Next to continue.")]
#[nwg_layout_item(layout: grid, row: 1, col: 0, col_span: 2, row_span: 4)]
description: nwg::Label,
#[nwg_control(text: "Next")]
#[nwg_layout_item(layout: grid, row: 5, col: 1)]
#[nwg_events(OnButtonClick: [InstallerWizard::next])]
next_button: nwg::Button,
#[nwg_control(text: "Cancel")]
#[nwg_layout_item(layout: grid, row: 5, col: 0)]
#[nwg_events(OnButtonClick: [InstallerWizard::exit])]
cancel_button: nwg::Button,
#[nwg_resource(family: "Segoe UI", size: 18, weight: 700)]
title_font: nwg::Font,
step: std::cell::RefCell<usize>,
}
impl InstallerWizard {
fn next(&self) {
let mut step = self.step.borrow_mut();
*step += 1;
match *step {
1 => self.install_step(),
2 => self.finish_step(),
_ => {}
}
}
fn install_step(&self) {
self.title.set_text("Installing...");
self.description.set_text("Please wait while OSTP is being installed.");
self.next_button.set_enabled(false);
// Perform installation
std::thread::spawn(move || {
if let Err(e) = perform_installation() {
eprintln!("Installation error: {}", e);
}
});
}
fn finish_step(&self) {
self.title.set_text("Installation Complete");
self.description.set_text("OSTP has been successfully installed.\n\nClick Finish to exit.");
self.next_button.set_text("Finish");
}
fn exit(&self) {
nwg::stop_thread_dispatch();
}
}
pub fn run_wizard() -> Result<()> {
nwg::init()?;
nwg::Font::set_global_family("Segoe UI")?;
let _app = InstallerWizard::default();
nwg::dispatch_thread_events();
Ok(())
}
fn perform_installation() -> Result<()> {
tracing::info!("Step 1: Installing Wintun DLL");
install_wintun_dll()?;
tracing::info!("Step 2: Registering Windows Service");
service::install_service()?;
tracing::info!("Step 3: Configuring Firewall");
firewall::add_firewall_rules()?;
tracing::info!("Installation complete");
Ok(())
}