Add VPN configuration tools and agent documentation

Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection
and enhanced agent documentation framework.

VPN Configuration (PST-NW-VPN):
- Setup-PST-L2TP-VPN.ps1: Automated L2TP/IPsec setup with split-tunnel and DNS
- Connect-PST-VPN.ps1: Connection helper with PPP adapter detection, DNS (192.168.0.2), and route config (192.168.0.0/24)
- Connect-PST-VPN-Standalone.ps1: Self-contained connection script for remote deployment
- Fix-PST-VPN-Auth.ps1: Authentication troubleshooting for CHAP/MSChapv2
- Diagnose-VPN-Interface.ps1: Comprehensive VPN interface and routing diagnostic
- Quick-Test-VPN.ps1: Fast connectivity verification (DNS/router/routes)
- Add-PST-VPN-Route-Manual.ps1: Manual route configuration helper
- vpn-connect.bat, vpn-disconnect.bat: Simple batch file shortcuts
- OpenVPN config files (Windows-compatible, abandoned for L2TP)

Key VPN Implementation Details:
- L2TP creates PPP adapter with connection name as interface description
- UniFi auto-configures DNS (192.168.0.2) but requires manual route to 192.168.0.0/24
- Split-tunnel enabled (only remote traffic through VPN)
- All-user connection for pre-login auto-connect via scheduled task
- Authentication: CHAP + MSChapv2 for UniFi compatibility

Agent Documentation:
- AGENT_QUICK_REFERENCE.md: Quick reference for all specialized agents
- documentation-squire.md: Documentation and task management specialist agent
- Updated all agent markdown files with standardized formatting

Project Organization:
- Moved conversation logs to dedicated directories (guru-connect-conversation-logs, guru-rmm-conversation-logs)
- Cleaned up old session JSONL files from projects/msp-tools/
- Added guru-connect infrastructure (agent, dashboard, proto, scripts, .gitea workflows)
- Added guru-rmm server components and deployment configs

Technical Notes:
- VPN IP pool: 192.168.4.x (client gets 192.168.4.6)
- Remote network: 192.168.0.0/24 (router at 192.168.0.10)
- PSK: rrClvnmUeXEFo90Ol+z7tfsAZHeSK6w7
- Credentials: pst-admin / 24Hearts$

Files: 15 VPN scripts, 2 agent docs, conversation log reorganization,
guru-connect/guru-rmm infrastructure additions

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-18 11:51:47 -07:00
parent b0a68d89bf
commit 6c316aa701
272 changed files with 37068 additions and 2 deletions

View File

@@ -0,0 +1,298 @@
//! Startup persistence for the agent
//!
//! Handles adding/removing the agent from Windows startup.
use anyhow::Result;
use tracing::{info, warn, error};
#[cfg(windows)]
use windows::Win32::System::Registry::{
RegOpenKeyExW, RegSetValueExW, RegDeleteValueW, RegCloseKey,
HKEY_CURRENT_USER, KEY_WRITE, REG_SZ,
};
#[cfg(windows)]
use windows::core::PCWSTR;
const STARTUP_KEY: &str = r"Software\Microsoft\Windows\CurrentVersion\Run";
const STARTUP_VALUE_NAME: &str = "GuruConnect";
/// Add the current executable to Windows startup
#[cfg(windows)]
pub fn add_to_startup() -> Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
// Get the path to the current executable
let exe_path = std::env::current_exe()?;
let exe_path_str = exe_path.to_string_lossy();
info!("Adding to startup: {}", exe_path_str);
// Convert strings to wide strings
let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let value_data: Vec<u16> = OsStr::new(&*exe_path_str)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let mut hkey = windows::Win32::Foundation::HANDLE::default();
// Open the Run key
let result = RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(key_path.as_ptr()),
0,
KEY_WRITE,
&mut hkey as *mut _ as *mut _,
);
if result.is_err() {
anyhow::bail!("Failed to open registry key: {:?}", result);
}
let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);
// Set the value
let data_bytes = std::slice::from_raw_parts(
value_data.as_ptr() as *const u8,
value_data.len() * 2,
);
let set_result = RegSetValueExW(
hkey_raw,
PCWSTR(value_name.as_ptr()),
0,
REG_SZ,
Some(data_bytes),
);
let _ = RegCloseKey(hkey_raw);
if set_result.is_err() {
anyhow::bail!("Failed to set registry value: {:?}", set_result);
}
}
info!("Successfully added to startup");
Ok(())
}
/// Remove the agent from Windows startup
#[cfg(windows)]
pub fn remove_from_startup() -> Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
info!("Removing from startup");
let key_path: Vec<u16> = OsStr::new(STARTUP_KEY)
.encode_wide()
.chain(std::iter::once(0))
.collect();
let value_name: Vec<u16> = OsStr::new(STARTUP_VALUE_NAME)
.encode_wide()
.chain(std::iter::once(0))
.collect();
unsafe {
let mut hkey = windows::Win32::Foundation::HANDLE::default();
let result = RegOpenKeyExW(
HKEY_CURRENT_USER,
PCWSTR(key_path.as_ptr()),
0,
KEY_WRITE,
&mut hkey as *mut _ as *mut _,
);
if result.is_err() {
warn!("Failed to open registry key for removal: {:?}", result);
return Ok(()); // Not an error if key doesn't exist
}
let hkey_raw = std::mem::transmute::<_, windows::Win32::System::Registry::HKEY>(hkey);
let delete_result = RegDeleteValueW(hkey_raw, PCWSTR(value_name.as_ptr()));
let _ = RegCloseKey(hkey_raw);
if delete_result.is_err() {
warn!("Registry value may not exist: {:?}", delete_result);
} else {
info!("Successfully removed from startup");
}
}
Ok(())
}
/// Full uninstall: remove from startup and delete the executable
#[cfg(windows)]
pub fn uninstall() -> Result<()> {
use std::ffi::OsStr;
use std::os::windows::ffi::OsStrExt;
use windows::Win32::Storage::FileSystem::{MoveFileExW, MOVEFILE_DELAY_UNTIL_REBOOT};
info!("Uninstalling agent");
// First remove from startup
let _ = remove_from_startup();
// Get the path to the current executable
let exe_path = std::env::current_exe()?;
let exe_path_str = exe_path.to_string_lossy();
info!("Scheduling deletion of: {}", exe_path_str);
// Convert path to wide string
let exe_wide: Vec<u16> = OsStr::new(&*exe_path_str)
.encode_wide()
.chain(std::iter::once(0))
.collect();
// Schedule the file for deletion on next reboot
// This is necessary because the executable is currently running
unsafe {
let result = MoveFileExW(
PCWSTR(exe_wide.as_ptr()),
PCWSTR::null(),
MOVEFILE_DELAY_UNTIL_REBOOT,
);
if result.is_err() {
warn!("Failed to schedule file deletion: {:?}. File may need manual removal.", result);
} else {
info!("Executable scheduled for deletion on reboot");
}
}
Ok(())
}
/// Install the SAS service if the binary is available
/// This allows the agent to send Ctrl+Alt+Del even without SYSTEM privileges
#[cfg(windows)]
pub fn install_sas_service() -> Result<()> {
info!("Attempting to install SAS service...");
// Check if the SAS service binary exists alongside the agent
let exe_path = std::env::current_exe()?;
let exe_dir = exe_path.parent().ok_or_else(|| anyhow::anyhow!("No parent directory"))?;
let sas_binary = exe_dir.join("guruconnect-sas-service.exe");
if !sas_binary.exists() {
// Also check in Program Files
let program_files = std::path::PathBuf::from(r"C:\Program Files\GuruConnect\guruconnect-sas-service.exe");
if !program_files.exists() {
warn!("SAS service binary not found");
return Ok(());
}
}
// Run the install command
let sas_path = if sas_binary.exists() {
sas_binary
} else {
std::path::PathBuf::from(r"C:\Program Files\GuruConnect\guruconnect-sas-service.exe")
};
let output = std::process::Command::new(&sas_path)
.arg("install")
.output();
match output {
Ok(result) => {
if result.status.success() {
info!("SAS service installed successfully");
} else {
let stderr = String::from_utf8_lossy(&result.stderr);
warn!("SAS service install failed: {}", stderr);
}
}
Err(e) => {
warn!("Failed to run SAS service installer: {}", e);
}
}
Ok(())
}
/// Uninstall the SAS service
#[cfg(windows)]
pub fn uninstall_sas_service() -> Result<()> {
info!("Attempting to uninstall SAS service...");
// Try to find and run the uninstall command
let paths = [
std::env::current_exe().ok().and_then(|p| p.parent().map(|d| d.join("guruconnect-sas-service.exe"))),
Some(std::path::PathBuf::from(r"C:\Program Files\GuruConnect\guruconnect-sas-service.exe")),
];
for path_opt in paths.iter() {
if let Some(ref path) = path_opt {
if path.exists() {
let output = std::process::Command::new(path)
.arg("uninstall")
.output();
if let Ok(result) = output {
if result.status.success() {
info!("SAS service uninstalled successfully");
return Ok(());
}
}
}
}
}
warn!("SAS service binary not found for uninstall");
Ok(())
}
/// Check if the SAS service is installed and running
#[cfg(windows)]
pub fn check_sas_service() -> bool {
use crate::sas_client;
sas_client::is_service_available()
}
#[cfg(not(windows))]
pub fn add_to_startup() -> Result<()> {
warn!("Startup persistence not implemented for this platform");
Ok(())
}
#[cfg(not(windows))]
pub fn remove_from_startup() -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn uninstall() -> Result<()> {
warn!("Uninstall not implemented for this platform");
Ok(())
}
#[cfg(not(windows))]
pub fn install_sas_service() -> Result<()> {
warn!("SAS service only available on Windows");
Ok(())
}
#[cfg(not(windows))]
pub fn uninstall_sas_service() -> Result<()> {
Ok(())
}
#[cfg(not(windows))]
pub fn check_sas_service() -> bool {
false
}