feat: Add Sequential Thinking to Code Review + Frontend Validation
Enhanced code review and frontend validation with intelligent triggers: Code Review Agent Enhancement: - Added Sequential Thinking MCP integration for complex issues - Triggers on 2+ rejections or 3+ critical issues - New escalation format with root cause analysis - Comprehensive solution strategies with trade-off evaluation - Educational feedback to break rejection cycles - Files: .claude/agents/code-review.md (+308 lines) - Docs: CODE_REVIEW_ST_ENHANCEMENT.md, CODE_REVIEW_ST_TESTING.md Frontend Design Skill Enhancement: - Automatic invocation for ANY UI change - Comprehensive validation checklist (200+ checkpoints) - 8 validation categories (visual, interactive, responsive, a11y, etc.) - 3 validation levels (quick, standard, comprehensive) - Integration with code review workflow - Files: .claude/skills/frontend-design/SKILL.md (+120 lines) - Docs: UI_VALIDATION_CHECKLIST.md (462 lines), AUTOMATIC_VALIDATION_ENHANCEMENT.md (587 lines) Settings Optimization: - Repaired .claude/settings.local.json (fixed m365 pattern) - Reduced permissions from 49 to 33 (33% reduction) - Removed duplicates, sorted alphabetically - Created SETTINGS_PERMISSIONS.md documentation Checkpoint Command Enhancement: - Dual checkpoint system (git + database) - Saves session context to API for cross-machine recall - Includes git metadata in database context - Files: .claude/commands/checkpoint.md (+139 lines) Decision Rationale: - Sequential Thinking MCP breaks rejection cycles by identifying root causes - Automatic frontend validation catches UI issues before code review - Dual checkpoints enable complete project memory across machines - Settings optimization improves maintainability Total: 1,200+ lines of documentation and enhancements Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,660 @@
|
||||
1→//! Windows Service implementation for GuruRMM Agent
|
||||
2→//!
|
||||
3→//! This module implements the Windows Service Control Manager (SCM) protocol,
|
||||
4→//! allowing the agent to run as a native Windows service without third-party wrappers.
|
||||
5→
|
||||
6→#[cfg(windows)]
|
||||
7→pub mod windows {
|
||||
8→ use std::ffi::OsString;
|
||||
9→ use std::path::PathBuf;
|
||||
10→ use std::sync::mpsc;
|
||||
11→ use std::time::Duration;
|
||||
12→
|
||||
13→ use anyhow::{Context, Result};
|
||||
14→ use tracing::{error, info, warn};
|
||||
15→ use windows_service::{
|
||||
16→ define_windows_service,
|
||||
17→ service::{
|
||||
18→ ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl,
|
||||
19→ ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus,
|
||||
20→ ServiceType,
|
||||
21→ },
|
||||
22→ service_control_handler::{self, ServiceControlHandlerResult},
|
||||
23→ service_dispatcher, service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
24→ };
|
||||
25→
|
||||
26→ pub const SERVICE_NAME: &str = "GuruRMMAgent";
|
||||
27→ pub const SERVICE_DISPLAY_NAME: &str = "GuruRMM Agent";
|
||||
28→ pub const SERVICE_DESCRIPTION: &str =
|
||||
29→ "GuruRMM Agent - Remote Monitoring and Management service";
|
||||
30→ pub const INSTALL_DIR: &str = r"C:\Program Files\GuruRMM";
|
||||
31→ pub const CONFIG_DIR: &str = r"C:\ProgramData\GuruRMM";
|
||||
32→
|
||||
33→ // Generate the Windows service boilerplate
|
||||
34→ define_windows_service!(ffi_service_main, service_main);
|
||||
35→
|
||||
36→ /// Entry point called by the Windows Service Control Manager
|
||||
37→ pub fn run_as_service() -> Result<()> {
|
||||
38→ // This function is called when Windows starts the service.
|
||||
39→ // It blocks until the service is stopped.
|
||||
40→ service_dispatcher::start(SERVICE_NAME, ffi_service_main)
|
||||
41→ .context("Failed to start service dispatcher")?;
|
||||
42→ Ok(())
|
||||
43→ }
|
||||
44→
|
||||
45→ /// Main service function called by the SCM
|
||||
46→ fn service_main(arguments: Vec<OsString>) {
|
||||
47→ if let Err(e) = run_service(arguments) {
|
||||
48→ error!("Service error: {}", e);
|
||||
49→ }
|
||||
50→ }
|
||||
51→
|
||||
52→ /// The actual service implementation
|
||||
53→ fn run_service(_arguments: Vec<OsString>) -> Result<()> {
|
||||
54→ // Create a channel to receive stop events
|
||||
55→ let (shutdown_tx, shutdown_rx) = mpsc::channel();
|
||||
56→
|
||||
57→ // Create the service control handler
|
||||
58→ let event_handler = move |control_event| -> ServiceControlHandlerResult {
|
||||
59→ match control_event {
|
||||
60→ ServiceControl::Stop => {
|
||||
61→ info!("Received stop command from SCM");
|
||||
62→ let _ = shutdown_tx.send(());
|
||||
63→ ServiceControlHandlerResult::NoError
|
||||
64→ }
|
||||
65→ ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
66→ ServiceControl::Shutdown => {
|
||||
67→ info!("Received shutdown command from SCM");
|
||||
68→ let _ = shutdown_tx.send(());
|
||||
69→ ServiceControlHandlerResult::NoError
|
||||
70→ }
|
||||
71→ _ => ServiceControlHandlerResult::NotImplemented,
|
||||
72→ }
|
||||
73→ };
|
||||
74→
|
||||
75→ // Register the service control handler
|
||||
76→ let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)
|
||||
77→ .context("Failed to register service control handler")?;
|
||||
78→
|
||||
79→ // Report that we're starting
|
||||
80→ status_handle
|
||||
81→ .set_service_status(ServiceStatus {
|
||||
82→ service_type: ServiceType::OWN_PROCESS,
|
||||
83→ current_state: ServiceState::StartPending,
|
||||
84→ controls_accepted: ServiceControlAccept::empty(),
|
||||
85→ exit_code: ServiceExitCode::Win32(0),
|
||||
86→ checkpoint: 0,
|
||||
87→ wait_hint: Duration::from_secs(10),
|
||||
88→ process_id: None,
|
||||
89→ })
|
||||
90→ .context("Failed to set StartPending status")?;
|
||||
91→
|
||||
92→ // Determine config path
|
||||
93→ let config_path = PathBuf::from(format!(r"{}\agent.toml", CONFIG_DIR));
|
||||
94→
|
||||
95→ // Create the tokio runtime for the agent
|
||||
96→ let runtime = tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?;
|
||||
97→
|
||||
98→ // Start the agent in the runtime
|
||||
99→ let agent_result = runtime.block_on(async {
|
||||
100→ // Load configuration
|
||||
101→ let config = match crate::config::AgentConfig::load(&config_path) {
|
||||
102→ Ok(c) => c,
|
||||
103→ Err(e) => {
|
||||
104→ error!("Failed to load config from {:?}: {}", config_path, e);
|
||||
105→ return Err(anyhow::anyhow!("Config load failed: {}", e));
|
||||
106→ }
|
||||
107→ };
|
||||
108→
|
||||
109→ info!("GuruRMM Agent service starting...");
|
||||
110→ info!("Config loaded from {:?}", config_path);
|
||||
111→ info!("Server URL: {}", config.server.url);
|
||||
112→
|
||||
113→ // Initialize metrics collector
|
||||
114→ let metrics_collector = crate::metrics::MetricsCollector::new();
|
||||
115→ info!("Metrics collector initialized");
|
||||
116→
|
||||
117→ // Create shared state
|
||||
118→ let state = std::sync::Arc::new(crate::AppState {
|
||||
119→ config: config.clone(),
|
||||
120→ metrics_collector,
|
||||
121→ connected: tokio::sync::RwLock::new(false),
|
||||
122→ });
|
||||
123→
|
||||
124→ // Report that we're running
|
||||
125→ status_handle
|
||||
126→ .set_service_status(ServiceStatus {
|
||||
127→ service_type: ServiceType::OWN_PROCESS,
|
||||
128→ current_state: ServiceState::Running,
|
||||
129→ controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
|
||||
130→ exit_code: ServiceExitCode::Win32(0),
|
||||
131→ checkpoint: 0,
|
||||
132→ wait_hint: Duration::default(),
|
||||
133→ process_id: None,
|
||||
134→ })
|
||||
135→ .context("Failed to set Running status")?;
|
||||
136→
|
||||
137→ // Start WebSocket client task
|
||||
138→ let ws_state = std::sync::Arc::clone(&state);
|
||||
139→ let ws_handle = tokio::spawn(async move {
|
||||
140→ loop {
|
||||
141→ info!("Connecting to server...");
|
||||
142→ match crate::transport::WebSocketClient::connect_and_run(std::sync::Arc::clone(
|
||||
143→ &ws_state,
|
||||
144→ ))
|
||||
145→ .await
|
||||
146→ {
|
||||
147→ Ok(_) => {
|
||||
148→ warn!("WebSocket connection closed normally, reconnecting...");
|
||||
149→ }
|
||||
150→ Err(e) => {
|
||||
151→ error!("WebSocket error: {}, reconnecting in 10 seconds...", e);
|
||||
152→ }
|
||||
153→ }
|
||||
154→ *ws_state.connected.write().await = false;
|
||||
155→ tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
156→ }
|
||||
157→ });
|
||||
158→
|
||||
159→ // Start metrics collection task
|
||||
160→ let metrics_state = std::sync::Arc::clone(&state);
|
||||
161→ let metrics_handle = tokio::spawn(async move {
|
||||
162→ let interval = metrics_state.config.metrics.interval_seconds;
|
||||
163→ let mut interval_timer =
|
||||
164→ tokio::time::interval(tokio::time::Duration::from_secs(interval));
|
||||
165→
|
||||
166→ loop {
|
||||
167→ interval_timer.tick().await;
|
||||
168→ let metrics = metrics_state.metrics_collector.collect().await;
|
||||
169→ if *metrics_state.connected.read().await {
|
||||
170→ info!(
|
||||
171→ "Metrics: CPU={:.1}%, Mem={:.1}%, Disk={:.1}%",
|
||||
172→ metrics.cpu_percent, metrics.memory_percent, metrics.disk_percent
|
||||
173→ );
|
||||
174→ }
|
||||
175→ }
|
||||
176→ });
|
||||
177→
|
||||
178→ // Wait for shutdown signal from SCM
|
||||
179→ // We use a separate task to poll the channel since it's not async
|
||||
180→ let shutdown_handle = tokio::spawn(async move {
|
||||
181→ loop {
|
||||
182→ match shutdown_rx.try_recv() {
|
||||
183→ Ok(_) => {
|
||||
184→ info!("Shutdown signal received");
|
||||
185→ break;
|
||||
186→ }
|
||||
187→ Err(mpsc::TryRecvError::Empty) => {
|
||||
188→ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
189→ }
|
||||
190→ Err(mpsc::TryRecvError::Disconnected) => {
|
||||
191→ warn!("Shutdown channel disconnected");
|
||||
192→ break;
|
||||
193→ }
|
||||
194→ }
|
||||
195→ }
|
||||
196→ });
|
||||
197→
|
||||
198→ // Wait for shutdown
|
||||
199→ tokio::select! {
|
||||
200→ _ = shutdown_handle => {
|
||||
201→ info!("Service shutting down gracefully");
|
||||
202→ }
|
||||
203→ _ = ws_handle => {
|
||||
204→ error!("WebSocket task ended unexpectedly");
|
||||
205→ }
|
||||
206→ _ = metrics_handle => {
|
||||
207→ error!("Metrics task ended unexpectedly");
|
||||
208→ }
|
||||
209→ }
|
||||
210→
|
||||
211→ Ok::<(), anyhow::Error>(())
|
||||
212→ });
|
||||
213→
|
||||
214→ // Report that we're stopping
|
||||
215→ status_handle
|
||||
216→ .set_service_status(ServiceStatus {
|
||||
217→ service_type: ServiceType::OWN_PROCESS,
|
||||
218→ current_state: ServiceState::StopPending,
|
||||
219→ controls_accepted: ServiceControlAccept::empty(),
|
||||
220→ exit_code: ServiceExitCode::Win32(0),
|
||||
221→ checkpoint: 0,
|
||||
222→ wait_hint: Duration::from_secs(5),
|
||||
223→ process_id: None,
|
||||
224→ })
|
||||
225→ .ok();
|
||||
226→
|
||||
227→ // Report that we've stopped
|
||||
228→ status_handle
|
||||
229→ .set_service_status(ServiceStatus {
|
||||
230→ service_type: ServiceType::OWN_PROCESS,
|
||||
231→ current_state: ServiceState::Stopped,
|
||||
232→ controls_accepted: ServiceControlAccept::empty(),
|
||||
233→ exit_code: match &agent_result {
|
||||
234→ Ok(_) => ServiceExitCode::Win32(0),
|
||||
235→ Err(_) => ServiceExitCode::Win32(1),
|
||||
236→ },
|
||||
237→ checkpoint: 0,
|
||||
238→ wait_hint: Duration::default(),
|
||||
239→ process_id: None,
|
||||
240→ })
|
||||
241→ .ok();
|
||||
242→
|
||||
243→ agent_result
|
||||
244→ }
|
||||
245→
|
||||
246→ /// Known legacy service names to check and remove
|
||||
247→ const LEGACY_SERVICE_NAMES: &[&str] = &[
|
||||
248→ "GuruRMM-Agent", // NSSM-based service name
|
||||
249→ "gururmm-agent", // Alternative casing
|
||||
250→ ];
|
||||
251→
|
||||
252→ /// Detect and remove legacy service installations (e.g., NSSM-based)
|
||||
253→ fn cleanup_legacy_services() -> Result<()> {
|
||||
254→ let manager = match ServiceManager::local_computer(
|
||||
255→ None::<&str>,
|
||||
256→ ServiceManagerAccess::CONNECT,
|
||||
257→ ) {
|
||||
258→ Ok(m) => m,
|
||||
259→ Err(_) => return Ok(()), // Can't connect, skip legacy cleanup
|
||||
260→ };
|
||||
261→
|
||||
262→ for legacy_name in LEGACY_SERVICE_NAMES {
|
||||
263→ if let Ok(service) = manager.open_service(
|
||||
264→ *legacy_name,
|
||||
265→ ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
|
||||
266→ ) {
|
||||
267→ info!("Found legacy service '{}', removing...", legacy_name);
|
||||
268→
|
||||
269→ // Stop if running
|
||||
270→ if let Ok(status) = service.query_status() {
|
||||
271→ if status.current_state != ServiceState::Stopped {
|
||||
272→ info!("Stopping legacy service...");
|
||||
273→ let _ = service.stop();
|
||||
274→ std::thread::sleep(Duration::from_secs(3));
|
||||
275→ }
|
||||
276→ }
|
||||
277→
|
||||
278→ // Delete the service
|
||||
279→ match service.delete() {
|
||||
280→ Ok(_) => {
|
||||
281→ println!("** Removed legacy service: {}", legacy_name);
|
||||
282→ }
|
||||
283→ Err(e) => {
|
||||
284→ warn!("Failed to delete legacy service '{}': {}", legacy_name, e);
|
||||
285→ }
|
||||
286→ }
|
||||
287→ }
|
||||
288→ }
|
||||
289→
|
||||
290→ // Also check for NSSM in registry/service config
|
||||
291→ // NSSM services have specific registry keys under HKLM\SYSTEM\CurrentControlSet\Services\{name}\Parameters
|
||||
292→ for legacy_name in LEGACY_SERVICE_NAMES {
|
||||
293→ let params_key = format!(
|
||||
294→ r"SYSTEM\CurrentControlSet\Services\{}\Parameters",
|
||||
295→ legacy_name
|
||||
296→ );
|
||||
297→ // If this key exists, it was likely an NSSM service
|
||||
298→ if let Ok(output) = std::process::Command::new("reg")
|
||||
299→ .args(["query", &format!(r"HKLM\{}", params_key)])
|
||||
300→ .output()
|
||||
301→ {
|
||||
302→ if output.status.success() {
|
||||
303→ info!("Found NSSM registry keys for '{}', cleaning up...", legacy_name);
|
||||
304→ let _ = std::process::Command::new("reg")
|
||||
305→ .args(["delete", &format!(r"HKLM\{}", params_key), "/f"])
|
||||
306→ .output();
|
||||
307→ }
|
||||
308→ }
|
||||
309→ }
|
||||
310→
|
||||
311→ Ok(())
|
||||
312→ }
|
||||
313→
|
||||
314→ /// Install the agent as a Windows service using native APIs
|
||||
315→ pub fn install(
|
||||
316→ server_url: Option<String>,
|
||||
317→ api_key: Option<String>,
|
||||
318→ skip_legacy_check: bool,
|
||||
319→ ) -> Result<()> {
|
||||
320→ info!("Installing GuruRMM Agent as Windows service...");
|
||||
321→
|
||||
322→ // Clean up legacy installations unless skipped
|
||||
323→ if !skip_legacy_check {
|
||||
324→ info!("Checking for legacy service installations...");
|
||||
325→ if let Err(e) = cleanup_legacy_services() {
|
||||
326→ warn!("Legacy cleanup warning: {}", e);
|
||||
327→ }
|
||||
328→ }
|
||||
329→
|
||||
330→ // Get the current executable path
|
||||
331→ let current_exe =
|
||||
332→ std::env::current_exe().context("Failed to get current executable path")?;
|
||||
333→
|
||||
334→ let binary_dest = PathBuf::from(format!(r"{}\gururmm-agent.exe", INSTALL_DIR));
|
||||
335→ let config_dest = PathBuf::from(format!(r"{}\agent.toml", CONFIG_DIR));
|
||||
336→
|
||||
337→ // Create directories
|
||||
338→ info!("Creating directories...");
|
||||
339→ std::fs::create_dir_all(INSTALL_DIR).context("Failed to create install directory")?;
|
||||
340→ std::fs::create_dir_all(CONFIG_DIR).context("Failed to create config directory")?;
|
||||
341→
|
||||
342→ // Copy binary
|
||||
343→ info!("Copying binary to: {:?}", binary_dest);
|
||||
344→ std::fs::copy(¤t_exe, &binary_dest).context("Failed to copy binary")?;
|
||||
345→
|
||||
346→ // Handle configuration
|
||||
347→ let config_needs_manual_edit;
|
||||
348→ if !config_dest.exists() {
|
||||
349→ info!("Creating config: {:?}", config_dest);
|
||||
350→
|
||||
351→ // Start with sample config
|
||||
352→ let mut config = crate::config::AgentConfig::sample();
|
||||
353→
|
||||
354→ // Apply provided values
|
||||
355→ if let Some(url) = &server_url {
|
||||
356→ config.server.url = url.clone();
|
||||
357→ }
|
||||
358→ if let Some(key) = &api_key {
|
||||
359→ config.server.api_key = key.clone();
|
||||
360→ }
|
||||
361→
|
||||
362→ let toml_str = toml::to_string_pretty(&config)?;
|
||||
363→ std::fs::write(&config_dest, toml_str).context("Failed to write config file")?;
|
||||
364→
|
||||
365→ config_needs_manual_edit = server_url.is_none() || api_key.is_none();
|
||||
366→ } else {
|
||||
367→ info!("Config already exists: {:?}", config_dest);
|
||||
368→ config_needs_manual_edit = false;
|
||||
369→
|
||||
370→ // If server_url or api_key provided, update existing config
|
||||
371→ if server_url.is_some() || api_key.is_some() {
|
||||
372→ info!("Updating existing configuration...");
|
||||
373→ let config_content = std::fs::read_to_string(&config_dest)?;
|
||||
374→ let mut config: crate::config::AgentConfig = toml::from_str(&config_content)
|
||||
375→ .context("Failed to parse existing config")?;
|
||||
376→
|
||||
377→ if let Some(url) = &server_url {
|
||||
378→ config.server.url = url.clone();
|
||||
379→ }
|
||||
380→ if let Some(key) = &api_key {
|
||||
381→ config.server.api_key = key.clone();
|
||||
382→ }
|
||||
383→
|
||||
384→ let toml_str = toml::to_string_pretty(&config)?;
|
||||
385→ std::fs::write(&config_dest, toml_str)
|
||||
386→ .context("Failed to update config file")?;
|
||||
387→ }
|
||||
388→ }
|
||||
389→
|
||||
390→ // Open the service manager
|
||||
391→ let manager = ServiceManager::local_computer(
|
||||
392→ None::<&str>,
|
||||
393→ ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE,
|
||||
394→ )
|
||||
395→ .context("Failed to connect to Service Control Manager. Run as Administrator.")?;
|
||||
396→
|
||||
397→ // Check if service already exists
|
||||
398→ if let Ok(service) = manager.open_service(
|
||||
399→ SERVICE_NAME,
|
||||
400→ ServiceAccess::QUERY_STATUS | ServiceAccess::DELETE | ServiceAccess::STOP,
|
||||
401→ ) {
|
||||
402→ info!("Removing existing service...");
|
||||
403→
|
||||
404→ // Stop the service if running
|
||||
405→ if let Ok(status) = service.query_status() {
|
||||
406→ if status.current_state != ServiceState::Stopped {
|
||||
407→ let _ = service.stop();
|
||||
408→ std::thread::sleep(Duration::from_secs(2));
|
||||
409→ }
|
||||
410→ }
|
||||
411→
|
||||
412→ // Delete the service
|
||||
413→ service.delete().context("Failed to delete existing service")?;
|
||||
414→ drop(service);
|
||||
415→
|
||||
416→ // Wait for deletion to complete
|
||||
417→ std::thread::sleep(Duration::from_secs(2));
|
||||
418→ }
|
||||
419→
|
||||
420→ // Create the service
|
||||
421→ // The service binary is called with "service" subcommand when started by SCM
|
||||
422→ let service_binary_path = format!(r#""{}" service"#, binary_dest.display());
|
||||
423→
|
||||
424→ info!("Creating service with path: {}", service_binary_path);
|
||||
425→
|
||||
426→ let service_info = ServiceInfo {
|
||||
427→ name: OsString::from(SERVICE_NAME),
|
||||
428→ display_name: OsString::from(SERVICE_DISPLAY_NAME),
|
||||
429→ service_type: ServiceType::OWN_PROCESS,
|
||||
430→ start_type: ServiceStartType::AutoStart,
|
||||
431→ error_control: ServiceErrorControl::Normal,
|
||||
432→ executable_path: binary_dest.clone(),
|
||||
433→ launch_arguments: vec![OsString::from("service")],
|
||||
434→ dependencies: vec![],
|
||||
435→ account_name: None, // LocalSystem
|
||||
436→ account_password: None,
|
||||
437→ };
|
||||
438→
|
||||
439→ let service = manager
|
||||
440→ .create_service(&service_info, ServiceAccess::CHANGE_CONFIG | ServiceAccess::START)
|
||||
441→ .context("Failed to create service")?;
|
||||
442→
|
||||
443→ // Set description
|
||||
444→ service
|
||||
445→ .set_description(SERVICE_DESCRIPTION)
|
||||
446→ .context("Failed to set service description")?;
|
||||
447→
|
||||
448→ // Configure recovery options using sc.exe (windows-service crate doesn't support this directly)
|
||||
449→ info!("Configuring recovery options...");
|
||||
450→ let _ = std::process::Command::new("sc")
|
||||
451→ .args([
|
||||
452→ "failure",
|
||||
453→ SERVICE_NAME,
|
||||
454→ "reset=86400",
|
||||
455→ "actions=restart/60000/restart/60000/restart/60000",
|
||||
456→ ])
|
||||
457→ .output();
|
||||
458→
|
||||
459→ println!("\n** GuruRMM Agent installed successfully!");
|
||||
460→ println!("\nInstalled files:");
|
||||
461→ println!(" Binary: {:?}", binary_dest);
|
||||
462→ println!(" Config: {:?}", config_dest);
|
||||
463→
|
||||
464→ if config_needs_manual_edit {
|
||||
465→ println!("\n** IMPORTANT: Edit {:?} with your server URL and API key!", config_dest);
|
||||
466→ println!("\nNext steps:");
|
||||
467→ println!(" 1. Edit {:?} with your server URL and API key", config_dest);
|
||||
468→ println!(" 2. Start the service:");
|
||||
469→ println!(" gururmm-agent start");
|
||||
470→ println!(" Or: sc start {}", SERVICE_NAME);
|
||||
471→ } else {
|
||||
472→ println!("\nStarting service...");
|
||||
473→ if let Err(e) = start() {
|
||||
474→ println!("** Failed to start service: {}. Start manually with:", e);
|
||||
475→ println!(" gururmm-agent start");
|
||||
476→ } else {
|
||||
477→ println!("** Service started successfully!");
|
||||
478→ }
|
||||
479→ }
|
||||
480→
|
||||
481→ println!("\nUseful commands:");
|
||||
482→ println!(" Status: gururmm-agent status");
|
||||
483→ println!(" Stop: gururmm-agent stop");
|
||||
484→ println!(" Start: gururmm-agent start");
|
||||
485→
|
||||
486→ Ok(())
|
||||
487→ }
|
||||
488→
|
||||
489→ /// Uninstall the Windows service
|
||||
490→ pub fn uninstall() -> Result<()> {
|
||||
491→ info!("Uninstalling GuruRMM Agent...");
|
||||
492→
|
||||
493→ let binary_path = PathBuf::from(format!(r"{}\gururmm-agent.exe", INSTALL_DIR));
|
||||
494→
|
||||
495→ // Open the service manager
|
||||
496→ let manager = ServiceManager::local_computer(
|
||||
497→ None::<&str>,
|
||||
498→ ServiceManagerAccess::CONNECT,
|
||||
499→ )
|
||||
500→ .context("Failed to connect to Service Control Manager. Run as Administrator.")?;
|
||||
501→
|
||||
502→ // Open the service
|
||||
503→ match manager.open_service(
|
||||
504→ SERVICE_NAME,
|
||||
505→ ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
|
||||
506→ ) {
|
||||
507→ Ok(service) => {
|
||||
508→ // Stop if running
|
||||
509→ if let Ok(status) = service.query_status() {
|
||||
510→ if status.current_state != ServiceState::Stopped {
|
||||
511→ info!("Stopping service...");
|
||||
512→ let _ = service.stop();
|
||||
513→ std::thread::sleep(Duration::from_secs(3));
|
||||
514→ }
|
||||
515→ }
|
||||
516→
|
||||
517→ // Delete the service
|
||||
518→ info!("Deleting service...");
|
||||
519→ service.delete().context("Failed to delete service")?;
|
||||
520→ }
|
||||
521→ Err(_) => {
|
||||
522→ warn!("Service was not installed");
|
||||
523→ }
|
||||
524→ }
|
||||
525→
|
||||
526→ // Remove binary
|
||||
527→ if binary_path.exists() {
|
||||
528→ info!("Removing binary: {:?}", binary_path);
|
||||
529→ // Wait a bit for service to fully stop
|
||||
530→ std::thread::sleep(Duration::from_secs(1));
|
||||
531→ if let Err(e) = std::fs::remove_file(&binary_path) {
|
||||
532→ warn!("Failed to remove binary (may be in use): {}", e);
|
||||
533→ }
|
||||
534→ }
|
||||
535→
|
||||
536→ // Remove install directory if empty
|
||||
537→ let _ = std::fs::remove_dir(INSTALL_DIR);
|
||||
538→
|
||||
539→ println!("\n** GuruRMM Agent uninstalled successfully!");
|
||||
540→ println!(
|
||||
541→ "\nNote: Config directory {:?} was preserved.",
|
||||
542→ CONFIG_DIR
|
||||
543→ );
|
||||
544→ println!("Remove it manually if no longer needed.");
|
||||
545→
|
||||
546→ Ok(())
|
||||
547→ }
|
||||
548→
|
||||
549→ /// Start the installed service
|
||||
550→ pub fn start() -> Result<()> {
|
||||
551→ info!("Starting GuruRMM Agent service...");
|
||||
552→
|
||||
553→ let manager = ServiceManager::local_computer(
|
||||
554→ None::<&str>,
|
||||
555→ ServiceManagerAccess::CONNECT,
|
||||
556→ )
|
||||
557→ .context("Failed to connect to Service Control Manager")?;
|
||||
558→
|
||||
559→ let service = manager
|
||||
560→ .open_service(SERVICE_NAME, ServiceAccess::START | ServiceAccess::QUERY_STATUS)
|
||||
561→ .context("Failed to open service. Is it installed?")?;
|
||||
562→
|
||||
563→ service
|
||||
564→ .start::<String>(&[])
|
||||
565→ .context("Failed to start service")?;
|
||||
566→
|
||||
567→ // Wait briefly and check status
|
||||
568→ std::thread::sleep(Duration::from_secs(2));
|
||||
569→
|
||||
570→ let status = service.query_status()?;
|
||||
571→ match status.current_state {
|
||||
572→ ServiceState::Running => {
|
||||
573→ println!("** Service started successfully");
|
||||
574→ println!("Check status: gururmm-agent status");
|
||||
575→ }
|
||||
576→ ServiceState::StartPending => {
|
||||
577→ println!("** Service is starting...");
|
||||
578→ println!("Check status: gururmm-agent status");
|
||||
579→ }
|
||||
580→ other => {
|
||||
581→ println!("Service state: {:?}", other);
|
||||
582→ }
|
||||
583→ }
|
||||
584→
|
||||
585→ Ok(())
|
||||
586→ }
|
||||
587→
|
||||
588→ /// Stop the installed service
|
||||
589→ pub fn stop() -> Result<()> {
|
||||
590→ info!("Stopping GuruRMM Agent service...");
|
||||
591→
|
||||
592→ let manager = ServiceManager::local_computer(
|
||||
593→ None::<&str>,
|
||||
594→ ServiceManagerAccess::CONNECT,
|
||||
595→ )
|
||||
596→ .context("Failed to connect to Service Control Manager")?;
|
||||
597→
|
||||
598→ let service = manager
|
||||
599→ .open_service(SERVICE_NAME, ServiceAccess::STOP | ServiceAccess::QUERY_STATUS)
|
||||
600→ .context("Failed to open service. Is it installed?")?;
|
||||
601→
|
||||
602→ service.stop().context("Failed to stop service")?;
|
||||
603→
|
||||
604→ // Wait and verify
|
||||
605→ std::thread::sleep(Duration::from_secs(2));
|
||||
606→
|
||||
607→ let status = service.query_status()?;
|
||||
608→ match status.current_state {
|
||||
609→ ServiceState::Stopped => {
|
||||
610→ println!("** Service stopped successfully");
|
||||
611→ }
|
||||
612→ ServiceState::StopPending => {
|
||||
613→ println!("** Service is stopping...");
|
||||
614→ }
|
||||
615→ other => {
|
||||
616→ println!("Service state: {:?}", other);
|
||||
617→ }
|
||||
618→ }
|
||||
619→
|
||||
620→ Ok(())
|
||||
621→ }
|
||||
622→
|
||||
623→ /// Query service status
|
||||
624→ pub fn status() -> Result<()> {
|
||||
625→ let manager = ServiceManager::local_computer(
|
||||
626→ None::<&str>,
|
||||
627→ ServiceManagerAccess::CONNECT,
|
||||
628→ )
|
||||
629→ .context("Failed to connect to Service Control Manager")?;
|
||||
630→
|
||||
631→ match manager.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS) {
|
||||
632→ Ok(service) => {
|
||||
633→ let status = service.query_status()?;
|
||||
634→ println!("GuruRMM Agent Service Status");
|
||||
635→ println!("============================");
|
||||
636→ println!("Service Name: {}", SERVICE_NAME);
|
||||
637→ println!("Display Name: {}", SERVICE_DISPLAY_NAME);
|
||||
638→ println!("State: {:?}", status.current_state);
|
||||
639→ println!(
|
||||
640→ "Binary: {}\\gururmm-agent.exe",
|
||||
641→ INSTALL_DIR
|
||||
642→ );
|
||||
643→ println!("Config: {}\\agent.toml", CONFIG_DIR);
|
||||
644→ }
|
||||
645→ Err(_) => {
|
||||
646→ println!("GuruRMM Agent Service Status");
|
||||
647→ println!("============================");
|
||||
648→ println!("Status: NOT INSTALLED");
|
||||
649→ println!("\nTo install: gururmm-agent install");
|
||||
650→ }
|
||||
651→ }
|
||||
652→
|
||||
653→ Ok(())
|
||||
654→ }
|
||||
655→}
|
||||
656→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,695 @@
|
||||
1→//! GuruRMM Agent - Cross-platform Remote Monitoring and Management Agent
|
||||
2→//!
|
||||
3→//! This agent connects to the GuruRMM server, reports system metrics,
|
||||
4→//! monitors services (watchdog), and executes remote commands.
|
||||
5→
|
||||
6→mod config;
|
||||
7→mod device_id;
|
||||
8→mod metrics;
|
||||
9→mod service;
|
||||
10→mod transport;
|
||||
11→mod updater;
|
||||
12→
|
||||
13→use anyhow::{Context, Result};
|
||||
14→use clap::{Parser, Subcommand};
|
||||
15→use std::path::PathBuf;
|
||||
16→use std::sync::Arc;
|
||||
17→use tokio::sync::RwLock;
|
||||
18→use tracing::{error, info, warn};
|
||||
19→
|
||||
20→use crate::config::AgentConfig;
|
||||
21→use crate::metrics::MetricsCollector;
|
||||
22→use crate::transport::WebSocketClient;
|
||||
23→
|
||||
24→/// GuruRMM Agent - Remote Monitoring and Management
|
||||
25→#[derive(Parser)]
|
||||
26→#[command(name = "gururmm-agent")]
|
||||
27→#[command(author, version, about, long_about = None)]
|
||||
28→struct Cli {
|
||||
29→ /// Path to configuration file
|
||||
30→ #[arg(short, long, default_value = "agent.toml")]
|
||||
31→ config: PathBuf,
|
||||
32→
|
||||
33→ /// Subcommand to run
|
||||
34→ #[command(subcommand)]
|
||||
35→ command: Option<Commands>,
|
||||
36→}
|
||||
37→
|
||||
38→#[derive(Subcommand)]
|
||||
39→enum Commands {
|
||||
40→ /// Run the agent (default)
|
||||
41→ Run,
|
||||
42→
|
||||
43→ /// Install as a system service
|
||||
44→ Install {
|
||||
45→ /// Server WebSocket URL (e.g., wss://rmm-api.example.com/ws)
|
||||
46→ #[arg(long)]
|
||||
47→ server_url: Option<String>,
|
||||
48→
|
||||
49→ /// API key for authentication
|
||||
50→ #[arg(long)]
|
||||
51→ api_key: Option<String>,
|
||||
52→
|
||||
53→ /// Skip legacy service detection and cleanup
|
||||
54→ #[arg(long, default_value = "false")]
|
||||
55→ skip_legacy_check: bool,
|
||||
56→ },
|
||||
57→
|
||||
58→ /// Uninstall the system service
|
||||
59→ Uninstall,
|
||||
60→
|
||||
61→ /// Start the installed service
|
||||
62→ Start,
|
||||
63→
|
||||
64→ /// Stop the installed service
|
||||
65→ Stop,
|
||||
66→
|
||||
67→ /// Show agent status
|
||||
68→ Status,
|
||||
69→
|
||||
70→ /// Generate a sample configuration file
|
||||
71→ GenerateConfig {
|
||||
72→ /// Output path for config file
|
||||
73→ #[arg(short, long, default_value = "agent.toml")]
|
||||
74→ output: PathBuf,
|
||||
75→ },
|
||||
76→
|
||||
77→ /// Run as Windows service (called by SCM, not for manual use)
|
||||
78→ #[command(hide = true)]
|
||||
79→ Service,
|
||||
80→}
|
||||
81→
|
||||
82→/// Shared application state
|
||||
83→pub struct AppState {
|
||||
84→ pub config: AgentConfig,
|
||||
85→ pub metrics_collector: MetricsCollector,
|
||||
86→ pub connected: RwLock<bool>,
|
||||
87→}
|
||||
88→
|
||||
89→#[tokio::main]
|
||||
90→async fn main() -> Result<()> {
|
||||
91→ // Initialize logging
|
||||
92→ tracing_subscriber::fmt()
|
||||
93→ .with_env_filter(
|
||||
94→ tracing_subscriber::EnvFilter::from_default_env()
|
||||
95→ .add_directive("gururmm_agent=info".parse()?)
|
||||
96→ .add_directive("info".parse()?),
|
||||
97→ )
|
||||
98→ .init();
|
||||
99→
|
||||
100→ let cli = Cli::parse();
|
||||
101→
|
||||
102→ match cli.command.unwrap_or(Commands::Run) {
|
||||
103→ Commands::Run => run_agent(cli.config).await,
|
||||
104→ Commands::Install { server_url, api_key, skip_legacy_check } => {
|
||||
105→ install_service(server_url, api_key, skip_legacy_check).await
|
||||
106→ }
|
||||
107→ Commands::Uninstall => uninstall_service().await,
|
||||
108→ Commands::Start => start_service().await,
|
||||
109→ Commands::Stop => stop_service().await,
|
||||
110→ Commands::Status => show_status(cli.config).await,
|
||||
111→ Commands::GenerateConfig { output } => generate_config(output).await,
|
||||
112→ Commands::Service => run_as_windows_service(),
|
||||
113→ }
|
||||
114→}
|
||||
115→
|
||||
116→/// Run as a Windows service (called by SCM)
|
||||
117→fn run_as_windows_service() -> Result<()> {
|
||||
118→ #[cfg(windows)]
|
||||
119→ {
|
||||
120→ service::windows::run_as_service()
|
||||
121→ }
|
||||
122→
|
||||
123→ #[cfg(not(windows))]
|
||||
124→ {
|
||||
125→ anyhow::bail!("Windows service mode is only available on Windows");
|
||||
126→ }
|
||||
127→}
|
||||
128→
|
||||
129→/// Main agent runtime loop
|
||||
130→async fn run_agent(config_path: PathBuf) -> Result<()> {
|
||||
131→ info!("GuruRMM Agent starting...");
|
||||
132→
|
||||
133→ // Load configuration
|
||||
134→ let config = AgentConfig::load(&config_path)?;
|
||||
135→ info!("Loaded configuration from {:?}", config_path);
|
||||
136→ info!("Server URL: {}", config.server.url);
|
||||
137→
|
||||
138→ // Initialize metrics collector
|
||||
139→ let metrics_collector = MetricsCollector::new();
|
||||
140→ info!("Metrics collector initialized");
|
||||
141→
|
||||
142→ // Create shared state
|
||||
143→ let state = Arc::new(AppState {
|
||||
144→ config: config.clone(),
|
||||
145→ metrics_collector,
|
||||
146→ connected: RwLock::new(false),
|
||||
147→ });
|
||||
148→
|
||||
149→ // Start the WebSocket client with auto-reconnect
|
||||
150→ let ws_state = Arc::clone(&state);
|
||||
151→ let ws_handle = tokio::spawn(async move {
|
||||
152→ loop {
|
||||
153→ info!("Connecting to server...");
|
||||
154→ match WebSocketClient::connect_and_run(Arc::clone(&ws_state)).await {
|
||||
155→ Ok(_) => {
|
||||
156→ warn!("WebSocket connection closed normally, reconnecting...");
|
||||
157→ }
|
||||
158→ Err(e) => {
|
||||
159→ error!("WebSocket error: {}, reconnecting in 10 seconds...", e);
|
||||
160→ }
|
||||
161→ }
|
||||
162→
|
||||
163→ // Mark as disconnected
|
||||
164→ *ws_state.connected.write().await = false;
|
||||
165→
|
||||
166→ // Wait before reconnecting
|
||||
167→ tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
168→ }
|
||||
169→ });
|
||||
170→
|
||||
171→ // Start metrics collection loop
|
||||
172→ let metrics_state = Arc::clone(&state);
|
||||
173→ let metrics_handle = tokio::spawn(async move {
|
||||
174→ let interval = metrics_state.config.metrics.interval_seconds;
|
||||
175→ let mut interval_timer = tokio::time::interval(tokio::time::Duration::from_secs(interval));
|
||||
176→
|
||||
177→ loop {
|
||||
178→ interval_timer.tick().await;
|
||||
179→
|
||||
180→ // Collect metrics (they'll be sent via WebSocket if connected)
|
||||
181→ let metrics = metrics_state.metrics_collector.collect().await;
|
||||
182→ if *metrics_state.connected.read().await {
|
||||
183→ info!(
|
||||
184→ "Metrics: CPU={:.1}%, Mem={:.1}%, Disk={:.1}%",
|
||||
185→ metrics.cpu_percent, metrics.memory_percent, metrics.disk_percent
|
||||
186→ );
|
||||
187→ }
|
||||
188→ }
|
||||
189→ });
|
||||
190→
|
||||
191→ // Wait for shutdown signal
|
||||
192→ tokio::select! {
|
||||
193→ _ = tokio::signal::ctrl_c() => {
|
||||
194→ info!("Received shutdown signal");
|
||||
195→ }
|
||||
196→ _ = ws_handle => {
|
||||
197→ error!("WebSocket task ended unexpectedly");
|
||||
198→ }
|
||||
199→ _ = metrics_handle => {
|
||||
200→ error!("Metrics task ended unexpectedly");
|
||||
201→ }
|
||||
202→ }
|
||||
203→
|
||||
204→ info!("GuruRMM Agent shutting down");
|
||||
205→ Ok(())
|
||||
206→}
|
||||
207→
|
||||
208→/// Install the agent as a system service
|
||||
209→async fn install_service(
|
||||
210→ server_url: Option<String>,
|
||||
211→ api_key: Option<String>,
|
||||
212→ skip_legacy_check: bool,
|
||||
213→) -> Result<()> {
|
||||
214→ #[cfg(windows)]
|
||||
215→ {
|
||||
216→ service::windows::install(server_url, api_key, skip_legacy_check)
|
||||
217→ }
|
||||
218→
|
||||
219→ #[cfg(target_os = "linux")]
|
||||
220→ {
|
||||
221→ install_systemd_service(server_url, api_key, skip_legacy_check).await
|
||||
222→ }
|
||||
223→
|
||||
224→ #[cfg(target_os = "macos")]
|
||||
225→ {
|
||||
226→ let _ = (server_url, api_key, skip_legacy_check); // Suppress unused warnings
|
||||
227→ info!("Installing GuruRMM Agent as launchd service...");
|
||||
228→ todo!("macOS launchd service installation not yet implemented");
|
||||
229→ }
|
||||
230→}
|
||||
231→
|
||||
232→/// Legacy service names to check for and clean up (Linux)
|
||||
233→#[cfg(target_os = "linux")]
|
||||
234→const LINUX_LEGACY_SERVICE_NAMES: &[&str] = &[
|
||||
235→ "gururmm", // Old name without -agent suffix
|
||||
236→ "guru-rmm-agent", // Alternative naming
|
||||
237→ "GuruRMM-Agent", // Case variant
|
||||
238→];
|
||||
239→
|
||||
240→/// Clean up legacy Linux service installations
|
||||
241→#[cfg(target_os = "linux")]
|
||||
242→fn cleanup_legacy_linux_services() -> Result<()> {
|
||||
243→ use std::process::Command;
|
||||
244→
|
||||
245→ info!("Checking for legacy service installations...");
|
||||
246→
|
||||
247→ for legacy_name in LINUX_LEGACY_SERVICE_NAMES {
|
||||
248→ // Check if service exists
|
||||
249→ let status = Command::new("systemctl")
|
||||
250→ .args(["status", legacy_name])
|
||||
251→ .output();
|
||||
252→
|
||||
253→ if let Ok(output) = status {
|
||||
254→ if output.status.success() || String::from_utf8_lossy(&output.stderr).contains("Loaded:") {
|
||||
255→ info!("Found legacy service '{}', removing...", legacy_name);
|
||||
256→
|
||||
257→ // Stop the service
|
||||
258→ let _ = Command::new("systemctl")
|
||||
259→ .args(["stop", legacy_name])
|
||||
260→ .status();
|
||||
261→
|
||||
262→ // Disable the service
|
||||
263→ let _ = Command::new("systemctl")
|
||||
264→ .args(["disable", legacy_name])
|
||||
265→ .status();
|
||||
266→
|
||||
267→ // Remove unit file
|
||||
268→ let unit_file = format!("/etc/systemd/system/{}.service", legacy_name);
|
||||
269→ if std::path::Path::new(&unit_file).exists() {
|
||||
270→ info!("Removing legacy unit file: {}", unit_file);
|
||||
271→ let _ = std::fs::remove_file(&unit_file);
|
||||
272→ }
|
||||
273→ }
|
||||
274→ }
|
||||
275→ }
|
||||
276→
|
||||
277→ // Check for legacy binaries in common locations
|
||||
278→ let legacy_binary_locations = [
|
||||
279→ "/usr/local/bin/gururmm",
|
||||
280→ "/usr/bin/gururmm",
|
||||
281→ "/opt/gururmm/gururmm",
|
||||
282→ "/opt/gururmm/agent",
|
||||
283→ ];
|
||||
284→
|
||||
285→ for legacy_path in legacy_binary_locations {
|
||||
286→ if std::path::Path::new(legacy_path).exists() {
|
||||
287→ info!("Found legacy binary at '{}', removing...", legacy_path);
|
||||
288→ let _ = std::fs::remove_file(legacy_path);
|
||||
289→ }
|
||||
290→ }
|
||||
291→
|
||||
292→ // Reload systemd to pick up removed unit files
|
||||
293→ let _ = Command::new("systemctl")
|
||||
294→ .args(["daemon-reload"])
|
||||
295→ .status();
|
||||
296→
|
||||
297→ Ok(())
|
||||
298→}
|
||||
299→
|
||||
300→/// Install as a systemd service (Linux)
|
||||
301→#[cfg(target_os = "linux")]
|
||||
302→async fn install_systemd_service(
|
||||
303→ server_url: Option<String>,
|
||||
304→ api_key: Option<String>,
|
||||
305→ skip_legacy_check: bool,
|
||||
306→) -> Result<()> {
|
||||
307→ use std::process::Command;
|
||||
308→
|
||||
309→ const SERVICE_NAME: &str = "gururmm-agent";
|
||||
310→ const INSTALL_DIR: &str = "/usr/local/bin";
|
||||
311→ const CONFIG_DIR: &str = "/etc/gururmm";
|
||||
312→ const SYSTEMD_DIR: &str = "/etc/systemd/system";
|
||||
313→
|
||||
314→ info!("Installing GuruRMM Agent as systemd service...");
|
||||
315→
|
||||
316→ // Check if running as root
|
||||
317→ if !nix::unistd::geteuid().is_root() {
|
||||
318→ anyhow::bail!("Installation requires root privileges. Please run with sudo.");
|
||||
319→ }
|
||||
320→
|
||||
321→ // Clean up legacy installations unless skipped
|
||||
322→ if !skip_legacy_check {
|
||||
323→ if let Err(e) = cleanup_legacy_linux_services() {
|
||||
324→ warn!("Legacy cleanup warning: {}", e);
|
||||
325→ }
|
||||
326→ }
|
||||
327→
|
||||
328→ // Get the current executable path
|
||||
329→ let current_exe = std::env::current_exe()
|
||||
330→ .context("Failed to get current executable path")?;
|
||||
331→
|
||||
332→ let binary_dest = format!("{}/{}", INSTALL_DIR, SERVICE_NAME);
|
||||
333→ let config_dest = format!("{}/agent.toml", CONFIG_DIR);
|
||||
334→ let unit_file = format!("{}/{}.service", SYSTEMD_DIR, SERVICE_NAME);
|
||||
335→
|
||||
336→ // Create config directory
|
||||
337→ info!("Creating config directory: {}", CONFIG_DIR);
|
||||
338→ std::fs::create_dir_all(CONFIG_DIR)
|
||||
339→ .context("Failed to create config directory")?;
|
||||
340→
|
||||
341→ // Copy binary
|
||||
342→ info!("Copying binary to: {}", binary_dest);
|
||||
343→ std::fs::copy(¤t_exe, &binary_dest)
|
||||
344→ .context("Failed to copy binary")?;
|
||||
345→
|
||||
346→ // Make binary executable
|
||||
347→ Command::new("chmod")
|
||||
348→ .args(["+x", &binary_dest])
|
||||
349→ .status()
|
||||
350→ .context("Failed to set binary permissions")?;
|
||||
351→
|
||||
352→ // Handle configuration
|
||||
353→ let config_needs_manual_edit;
|
||||
354→ if !std::path::Path::new(&config_dest).exists() {
|
||||
355→ info!("Creating config: {}", config_dest);
|
||||
356→
|
||||
357→ // Start with sample config
|
||||
358→ let mut config = crate::config::AgentConfig::sample();
|
||||
359→
|
||||
360→ // Apply provided values
|
||||
361→ if let Some(url) = &server_url {
|
||||
362→ config.server.url = url.clone();
|
||||
363→ }
|
||||
364→ if let Some(key) = &api_key {
|
||||
365→ config.server.api_key = key.clone();
|
||||
366→ }
|
||||
367→
|
||||
368→ let toml_str = toml::to_string_pretty(&config)?;
|
||||
369→ std::fs::write(&config_dest, toml_str)
|
||||
370→ .context("Failed to write config file")?;
|
||||
371→
|
||||
372→ // Set restrictive permissions on config (contains API key)
|
||||
373→ Command::new("chmod")
|
||||
374→ .args(["600", &config_dest])
|
||||
375→ .status()
|
||||
376→ .context("Failed to set config permissions")?;
|
||||
377→
|
||||
378→ config_needs_manual_edit = server_url.is_none() || api_key.is_none();
|
||||
379→ } else {
|
||||
380→ info!("Config already exists: {}", config_dest);
|
||||
381→ config_needs_manual_edit = false;
|
||||
382→
|
||||
383→ // If server_url or api_key provided, update existing config
|
||||
384→ if server_url.is_some() || api_key.is_some() {
|
||||
385→ info!("Updating existing configuration...");
|
||||
386→ let config_content = std::fs::read_to_string(&config_dest)?;
|
||||
387→ let mut config: crate::config::AgentConfig = toml::from_str(&config_content)
|
||||
388→ .context("Failed to parse existing config")?;
|
||||
389→
|
||||
390→ if let Some(url) = &server_url {
|
||||
391→ config.server.url = url.clone();
|
||||
392→ }
|
||||
393→ if let Some(key) = &api_key {
|
||||
394→ config.server.api_key = key.clone();
|
||||
395→ }
|
||||
396→
|
||||
397→ let toml_str = toml::to_string_pretty(&config)?;
|
||||
398→ std::fs::write(&config_dest, toml_str)
|
||||
399→ .context("Failed to update config file")?;
|
||||
400→ }
|
||||
401→ }
|
||||
402→
|
||||
403→ // Create systemd unit file
|
||||
404→ let unit_content = format!(r#"[Unit]
|
||||
405→Description=GuruRMM Agent - Remote Monitoring and Management
|
||||
406→Documentation=https://github.com/azcomputerguru/gururmm
|
||||
407→After=network-online.target
|
||||
408→Wants=network-online.target
|
||||
409→
|
||||
410→[Service]
|
||||
411→Type=simple
|
||||
412→ExecStart={binary} --config {config} run
|
||||
413→Restart=always
|
||||
414→RestartSec=10
|
||||
415→StandardOutput=journal
|
||||
416→StandardError=journal
|
||||
417→SyslogIdentifier={service}
|
||||
418→
|
||||
419→# Security hardening
|
||||
420→NoNewPrivileges=true
|
||||
421→ProtectSystem=strict
|
||||
422→ProtectHome=read-only
|
||||
423→PrivateTmp=true
|
||||
424→ReadWritePaths=/var/log
|
||||
425→
|
||||
426→[Install]
|
||||
427→WantedBy=multi-user.target
|
||||
428→"#,
|
||||
429→ binary = binary_dest,
|
||||
430→ config = config_dest,
|
||||
431→ service = SERVICE_NAME
|
||||
432→ );
|
||||
433→
|
||||
434→ info!("Creating systemd unit file: {}", unit_file);
|
||||
435→ std::fs::write(&unit_file, unit_content)
|
||||
436→ .context("Failed to write systemd unit file")?;
|
||||
437→
|
||||
438→ // Reload systemd daemon
|
||||
439→ info!("Reloading systemd daemon...");
|
||||
440→ let status = Command::new("systemctl")
|
||||
441→ .args(["daemon-reload"])
|
||||
442→ .status()
|
||||
443→ .context("Failed to reload systemd")?;
|
||||
444→
|
||||
445→ if !status.success() {
|
||||
446→ anyhow::bail!("systemctl daemon-reload failed");
|
||||
447→ }
|
||||
448→
|
||||
449→ // Enable the service
|
||||
450→ info!("Enabling service...");
|
||||
451→ let status = Command::new("systemctl")
|
||||
452→ .args(["enable", SERVICE_NAME])
|
||||
453→ .status()
|
||||
454→ .context("Failed to enable service")?;
|
||||
455→
|
||||
456→ if !status.success() {
|
||||
457→ anyhow::bail!("systemctl enable failed");
|
||||
458→ }
|
||||
459→
|
||||
460→ println!("\n✓ GuruRMM Agent installed successfully!");
|
||||
461→ println!("\nInstalled files:");
|
||||
462→ println!(" Binary: {}", binary_dest);
|
||||
463→ println!(" Config: {}", config_dest);
|
||||
464→ println!(" Service: {}", unit_file);
|
||||
465→
|
||||
466→ if config_needs_manual_edit {
|
||||
467→ println!("\n⚠️ IMPORTANT: Edit {} with your server URL and API key!", config_dest);
|
||||
468→ println!("\nNext steps:");
|
||||
469→ println!(" 1. Edit {} with your server URL and API key", config_dest);
|
||||
470→ println!(" 2. Start the service: sudo systemctl start {}", SERVICE_NAME);
|
||||
471→ } else {
|
||||
472→ println!("\nStarting service...");
|
||||
473→ let status = Command::new("systemctl")
|
||||
474→ .args(["start", SERVICE_NAME])
|
||||
475→ .status();
|
||||
476→
|
||||
477→ if status.is_ok() && status.unwrap().success() {
|
||||
478→ println!("✓ Service started successfully!");
|
||||
479→ } else {
|
||||
480→ println!("⚠️ Failed to start service. Check logs: sudo journalctl -u {} -f", SERVICE_NAME);
|
||||
481→ }
|
||||
482→ }
|
||||
483→
|
||||
484→ println!("\nUseful commands:");
|
||||
485→ println!(" Status: sudo systemctl status {}", SERVICE_NAME);
|
||||
486→ println!(" Logs: sudo journalctl -u {} -f", SERVICE_NAME);
|
||||
487→ println!(" Stop: sudo systemctl stop {}", SERVICE_NAME);
|
||||
488→ println!(" Start: sudo systemctl start {}", SERVICE_NAME);
|
||||
489→
|
||||
490→ Ok(())
|
||||
491→}
|
||||
492→
|
||||
493→/// Uninstall the system service
|
||||
494→async fn uninstall_service() -> Result<()> {
|
||||
495→ #[cfg(windows)]
|
||||
496→ {
|
||||
497→ service::windows::uninstall()
|
||||
498→ }
|
||||
499→
|
||||
500→ #[cfg(target_os = "linux")]
|
||||
501→ {
|
||||
502→ uninstall_systemd_service().await
|
||||
503→ }
|
||||
504→
|
||||
505→ #[cfg(target_os = "macos")]
|
||||
506→ {
|
||||
507→ todo!("macOS service uninstallation not yet implemented");
|
||||
508→ }
|
||||
509→}
|
||||
510→
|
||||
511→/// Uninstall systemd service (Linux)
|
||||
512→#[cfg(target_os = "linux")]
|
||||
513→async fn uninstall_systemd_service() -> Result<()> {
|
||||
514→ use std::process::Command;
|
||||
515→
|
||||
516→ const SERVICE_NAME: &str = "gururmm-agent";
|
||||
517→ const INSTALL_DIR: &str = "/usr/local/bin";
|
||||
518→ const CONFIG_DIR: &str = "/etc/gururmm";
|
||||
519→ const SYSTEMD_DIR: &str = "/etc/systemd/system";
|
||||
520→
|
||||
521→ info!("Uninstalling GuruRMM Agent...");
|
||||
522→
|
||||
523→ if !nix::unistd::geteuid().is_root() {
|
||||
524→ anyhow::bail!("Uninstallation requires root privileges. Please run with sudo.");
|
||||
525→ }
|
||||
526→
|
||||
527→ let binary_path = format!("{}/{}", INSTALL_DIR, SERVICE_NAME);
|
||||
528→ let unit_file = format!("{}/{}.service", SYSTEMD_DIR, SERVICE_NAME);
|
||||
529→
|
||||
530→ // Stop the service if running
|
||||
531→ info!("Stopping service...");
|
||||
532→ let _ = Command::new("systemctl")
|
||||
533→ .args(["stop", SERVICE_NAME])
|
||||
534→ .status();
|
||||
535→
|
||||
536→ // Disable the service
|
||||
537→ info!("Disabling service...");
|
||||
538→ let _ = Command::new("systemctl")
|
||||
539→ .args(["disable", SERVICE_NAME])
|
||||
540→ .status();
|
||||
541→
|
||||
542→ // Remove unit file
|
||||
543→ if std::path::Path::new(&unit_file).exists() {
|
||||
544→ info!("Removing unit file: {}", unit_file);
|
||||
545→ std::fs::remove_file(&unit_file)?;
|
||||
546→ }
|
||||
547→
|
||||
548→ // Remove binary
|
||||
549→ if std::path::Path::new(&binary_path).exists() {
|
||||
550→ info!("Removing binary: {}", binary_path);
|
||||
551→ std::fs::remove_file(&binary_path)?;
|
||||
552→ }
|
||||
553→
|
||||
554→ // Reload systemd
|
||||
555→ let _ = Command::new("systemctl")
|
||||
556→ .args(["daemon-reload"])
|
||||
557→ .status();
|
||||
558→
|
||||
559→ println!("\n✓ GuruRMM Agent uninstalled successfully!");
|
||||
560→ println!("\nNote: Config directory {} was preserved.", CONFIG_DIR);
|
||||
561→ println!("Remove it manually if no longer needed: sudo rm -rf {}", CONFIG_DIR);
|
||||
562→
|
||||
563→ Ok(())
|
||||
564→}
|
||||
565→
|
||||
566→/// Start the installed service
|
||||
567→async fn start_service() -> Result<()> {
|
||||
568→ #[cfg(windows)]
|
||||
569→ {
|
||||
570→ service::windows::start()
|
||||
571→ }
|
||||
572→
|
||||
573→ #[cfg(target_os = "linux")]
|
||||
574→ {
|
||||
575→ use std::process::Command;
|
||||
576→
|
||||
577→ info!("Starting GuruRMM Agent service...");
|
||||
578→
|
||||
579→ let status = Command::new("systemctl")
|
||||
580→ .args(["start", "gururmm-agent"])
|
||||
581→ .status()
|
||||
582→ .context("Failed to start service")?;
|
||||
583→
|
||||
584→ if status.success() {
|
||||
585→ println!("** Service started successfully");
|
||||
586→ println!("Check status: sudo systemctl status gururmm-agent");
|
||||
587→ } else {
|
||||
588→ anyhow::bail!("Failed to start service. Check: sudo journalctl -u gururmm-agent -n 50");
|
||||
589→ }
|
||||
590→
|
||||
591→ Ok(())
|
||||
592→ }
|
||||
593→
|
||||
594→ #[cfg(target_os = "macos")]
|
||||
595→ {
|
||||
596→ todo!("macOS service start not yet implemented");
|
||||
597→ }
|
||||
598→}
|
||||
599→
|
||||
600→/// Stop the installed service
|
||||
601→async fn stop_service() -> Result<()> {
|
||||
602→ #[cfg(windows)]
|
||||
603→ {
|
||||
604→ service::windows::stop()
|
||||
605→ }
|
||||
606→
|
||||
607→ #[cfg(target_os = "linux")]
|
||||
608→ {
|
||||
609→ use std::process::Command;
|
||||
610→
|
||||
611→ info!("Stopping GuruRMM Agent service...");
|
||||
612→
|
||||
613→ let status = Command::new("systemctl")
|
||||
614→ .args(["stop", "gururmm-agent"])
|
||||
615→ .status()
|
||||
616→ .context("Failed to stop service")?;
|
||||
617→
|
||||
618→ if status.success() {
|
||||
619→ println!("** Service stopped successfully");
|
||||
620→ } else {
|
||||
621→ anyhow::bail!("Failed to stop service");
|
||||
622→ }
|
||||
623→
|
||||
624→ Ok(())
|
||||
625→ }
|
||||
626→
|
||||
627→ #[cfg(target_os = "macos")]
|
||||
628→ {
|
||||
629→ todo!("macOS service stop not yet implemented");
|
||||
630→ }
|
||||
631→}
|
||||
632→
|
||||
633→/// Show agent status
|
||||
634→async fn show_status(config_path: PathBuf) -> Result<()> {
|
||||
635→ // On Windows, show service status
|
||||
636→ #[cfg(windows)]
|
||||
637→ {
|
||||
638→ service::windows::status()?;
|
||||
639→ println!();
|
||||
640→ }
|
||||
641→
|
||||
642→ // Try to load config for additional info
|
||||
643→ match AgentConfig::load(&config_path) {
|
||||
644→ Ok(config) => {
|
||||
645→ println!("Configuration");
|
||||
646→ println!("=============");
|
||||
647→ println!("Config file: {:?}", config_path);
|
||||
648→ println!("Server URL: {}", config.server.url);
|
||||
649→ println!("Metrics interval: {} seconds", config.metrics.interval_seconds);
|
||||
650→ println!("Watchdog enabled: {}", config.watchdog.enabled);
|
||||
651→
|
||||
652→ // Collect current metrics
|
||||
653→ let collector = MetricsCollector::new();
|
||||
654→ let metrics = collector.collect().await;
|
||||
655→
|
||||
656→ println!("\nCurrent System Metrics:");
|
||||
657→ println!(" CPU Usage: {:.1}%", metrics.cpu_percent);
|
||||
658→ println!(" Memory Usage: {:.1}%", metrics.memory_percent);
|
||||
659→ println!(
|
||||
660→ " Memory Used: {:.2} GB",
|
||||
661→ metrics.memory_used_bytes as f64 / 1_073_741_824.0
|
||||
662→ );
|
||||
663→ println!(" Disk Usage: {:.1}%", metrics.disk_percent);
|
||||
664→ println!(
|
||||
665→ " Disk Used: {:.2} GB",
|
||||
666→ metrics.disk_used_bytes as f64 / 1_073_741_824.0
|
||||
667→ );
|
||||
668→ }
|
||||
669→ Err(_) => {
|
||||
670→ println!("\nConfig file {:?} not found or invalid.", config_path);
|
||||
671→ #[cfg(windows)]
|
||||
672→ println!("Service config location: {}\\agent.toml", service::windows::CONFIG_DIR);
|
||||
673→ }
|
||||
674→ }
|
||||
675→
|
||||
676→ Ok(())
|
||||
677→}
|
||||
678→
|
||||
679→/// Generate a sample configuration file
|
||||
680→async fn generate_config(output: PathBuf) -> Result<()> {
|
||||
681→ let sample_config = AgentConfig::sample();
|
||||
682→ let toml_str = toml::to_string_pretty(&sample_config)?;
|
||||
683→
|
||||
684→ std::fs::write(&output, toml_str)?;
|
||||
685→ println!("Sample configuration written to {:?}", output);
|
||||
686→ println!("\nEdit this file with your server URL and API key, then run:");
|
||||
687→ println!(" gururmm-agent --config {:?} run", output);
|
||||
688→
|
||||
689→ Ok(())
|
||||
690→}
|
||||
691→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,660 @@
|
||||
1→//! Windows Service implementation for GuruRMM Agent
|
||||
2→//!
|
||||
3→//! This module implements the Windows Service Control Manager (SCM) protocol,
|
||||
4→//! allowing the agent to run as a native Windows service without third-party wrappers.
|
||||
5→
|
||||
6→#[cfg(windows)]
|
||||
7→pub mod windows {
|
||||
8→ use std::ffi::OsString;
|
||||
9→ use std::path::PathBuf;
|
||||
10→ use std::sync::mpsc;
|
||||
11→ use std::time::Duration;
|
||||
12→
|
||||
13→ use anyhow::{Context, Result};
|
||||
14→ use tracing::{error, info, warn};
|
||||
15→ use windows_service::{
|
||||
16→ define_windows_service,
|
||||
17→ service::{
|
||||
18→ ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl,
|
||||
19→ ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus,
|
||||
20→ ServiceType,
|
||||
21→ },
|
||||
22→ service_control_handler::{self, ServiceControlHandlerResult},
|
||||
23→ service_dispatcher, service_manager::{ServiceManager, ServiceManagerAccess},
|
||||
24→ };
|
||||
25→
|
||||
26→ pub const SERVICE_NAME: &str = "GuruRMMAgent";
|
||||
27→ pub const SERVICE_DISPLAY_NAME: &str = "GuruRMM Agent";
|
||||
28→ pub const SERVICE_DESCRIPTION: &str =
|
||||
29→ "GuruRMM Agent - Remote Monitoring and Management service";
|
||||
30→ pub const INSTALL_DIR: &str = r"C:\Program Files\GuruRMM";
|
||||
31→ pub const CONFIG_DIR: &str = r"C:\ProgramData\GuruRMM";
|
||||
32→
|
||||
33→ // Generate the Windows service boilerplate
|
||||
34→ define_windows_service!(ffi_service_main, service_main);
|
||||
35→
|
||||
36→ /// Entry point called by the Windows Service Control Manager
|
||||
37→ pub fn run_as_service() -> Result<()> {
|
||||
38→ // This function is called when Windows starts the service.
|
||||
39→ // It blocks until the service is stopped.
|
||||
40→ service_dispatcher::start(SERVICE_NAME, ffi_service_main)
|
||||
41→ .context("Failed to start service dispatcher")?;
|
||||
42→ Ok(())
|
||||
43→ }
|
||||
44→
|
||||
45→ /// Main service function called by the SCM
|
||||
46→ fn service_main(arguments: Vec<OsString>) {
|
||||
47→ if let Err(e) = run_service(arguments) {
|
||||
48→ error!("Service error: {}", e);
|
||||
49→ }
|
||||
50→ }
|
||||
51→
|
||||
52→ /// The actual service implementation
|
||||
53→ fn run_service(_arguments: Vec<OsString>) -> Result<()> {
|
||||
54→ // Create a channel to receive stop events
|
||||
55→ let (shutdown_tx, shutdown_rx) = mpsc::channel();
|
||||
56→
|
||||
57→ // Create the service control handler
|
||||
58→ let event_handler = move |control_event| -> ServiceControlHandlerResult {
|
||||
59→ match control_event {
|
||||
60→ ServiceControl::Stop => {
|
||||
61→ info!("Received stop command from SCM");
|
||||
62→ let _ = shutdown_tx.send(());
|
||||
63→ ServiceControlHandlerResult::NoError
|
||||
64→ }
|
||||
65→ ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
|
||||
66→ ServiceControl::Shutdown => {
|
||||
67→ info!("Received shutdown command from SCM");
|
||||
68→ let _ = shutdown_tx.send(());
|
||||
69→ ServiceControlHandlerResult::NoError
|
||||
70→ }
|
||||
71→ _ => ServiceControlHandlerResult::NotImplemented,
|
||||
72→ }
|
||||
73→ };
|
||||
74→
|
||||
75→ // Register the service control handler
|
||||
76→ let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)
|
||||
77→ .context("Failed to register service control handler")?;
|
||||
78→
|
||||
79→ // Report that we're starting
|
||||
80→ status_handle
|
||||
81→ .set_service_status(ServiceStatus {
|
||||
82→ service_type: ServiceType::OWN_PROCESS,
|
||||
83→ current_state: ServiceState::StartPending,
|
||||
84→ controls_accepted: ServiceControlAccept::empty(),
|
||||
85→ exit_code: ServiceExitCode::Win32(0),
|
||||
86→ checkpoint: 0,
|
||||
87→ wait_hint: Duration::from_secs(10),
|
||||
88→ process_id: None,
|
||||
89→ })
|
||||
90→ .context("Failed to set StartPending status")?;
|
||||
91→
|
||||
92→ // Determine config path
|
||||
93→ let config_path = PathBuf::from(format!(r"{}\agent.toml", CONFIG_DIR));
|
||||
94→
|
||||
95→ // Create the tokio runtime for the agent
|
||||
96→ let runtime = tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?;
|
||||
97→
|
||||
98→ // Start the agent in the runtime
|
||||
99→ let agent_result = runtime.block_on(async {
|
||||
100→ // Load configuration
|
||||
101→ let config = match crate::config::AgentConfig::load(&config_path) {
|
||||
102→ Ok(c) => c,
|
||||
103→ Err(e) => {
|
||||
104→ error!("Failed to load config from {:?}: {}", config_path, e);
|
||||
105→ return Err(anyhow::anyhow!("Config load failed: {}", e));
|
||||
106→ }
|
||||
107→ };
|
||||
108→
|
||||
109→ info!("GuruRMM Agent service starting...");
|
||||
110→ info!("Config loaded from {:?}", config_path);
|
||||
111→ info!("Server URL: {}", config.server.url);
|
||||
112→
|
||||
113→ // Initialize metrics collector
|
||||
114→ let metrics_collector = crate::metrics::MetricsCollector::new();
|
||||
115→ info!("Metrics collector initialized");
|
||||
116→
|
||||
117→ // Create shared state
|
||||
118→ let state = std::sync::Arc::new(crate::AppState {
|
||||
119→ config: config.clone(),
|
||||
120→ metrics_collector,
|
||||
121→ connected: tokio::sync::RwLock::new(false),
|
||||
122→ });
|
||||
123→
|
||||
124→ // Report that we're running
|
||||
125→ status_handle
|
||||
126→ .set_service_status(ServiceStatus {
|
||||
127→ service_type: ServiceType::OWN_PROCESS,
|
||||
128→ current_state: ServiceState::Running,
|
||||
129→ controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
|
||||
130→ exit_code: ServiceExitCode::Win32(0),
|
||||
131→ checkpoint: 0,
|
||||
132→ wait_hint: Duration::default(),
|
||||
133→ process_id: None,
|
||||
134→ })
|
||||
135→ .context("Failed to set Running status")?;
|
||||
136→
|
||||
137→ // Start WebSocket client task
|
||||
138→ let ws_state = std::sync::Arc::clone(&state);
|
||||
139→ let ws_handle = tokio::spawn(async move {
|
||||
140→ loop {
|
||||
141→ info!("Connecting to server...");
|
||||
142→ match crate::transport::WebSocketClient::connect_and_run(std::sync::Arc::clone(
|
||||
143→ &ws_state,
|
||||
144→ ))
|
||||
145→ .await
|
||||
146→ {
|
||||
147→ Ok(_) => {
|
||||
148→ warn!("WebSocket connection closed normally, reconnecting...");
|
||||
149→ }
|
||||
150→ Err(e) => {
|
||||
151→ error!("WebSocket error: {}, reconnecting in 10 seconds...", e);
|
||||
152→ }
|
||||
153→ }
|
||||
154→ *ws_state.connected.write().await = false;
|
||||
155→ tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
|
||||
156→ }
|
||||
157→ });
|
||||
158→
|
||||
159→ // Start metrics collection task
|
||||
160→ let metrics_state = std::sync::Arc::clone(&state);
|
||||
161→ let metrics_handle = tokio::spawn(async move {
|
||||
162→ let interval = metrics_state.config.metrics.interval_seconds;
|
||||
163→ let mut interval_timer =
|
||||
164→ tokio::time::interval(tokio::time::Duration::from_secs(interval));
|
||||
165→
|
||||
166→ loop {
|
||||
167→ interval_timer.tick().await;
|
||||
168→ let metrics = metrics_state.metrics_collector.collect().await;
|
||||
169→ if *metrics_state.connected.read().await {
|
||||
170→ info!(
|
||||
171→ "Metrics: CPU={:.1}%, Mem={:.1}%, Disk={:.1}%",
|
||||
172→ metrics.cpu_percent, metrics.memory_percent, metrics.disk_percent
|
||||
173→ );
|
||||
174→ }
|
||||
175→ }
|
||||
176→ });
|
||||
177→
|
||||
178→ // Wait for shutdown signal from SCM
|
||||
179→ // We use a separate task to poll the channel since it's not async
|
||||
180→ let shutdown_handle = tokio::spawn(async move {
|
||||
181→ loop {
|
||||
182→ match shutdown_rx.try_recv() {
|
||||
183→ Ok(_) => {
|
||||
184→ info!("Shutdown signal received");
|
||||
185→ break;
|
||||
186→ }
|
||||
187→ Err(mpsc::TryRecvError::Empty) => {
|
||||
188→ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
|
||||
189→ }
|
||||
190→ Err(mpsc::TryRecvError::Disconnected) => {
|
||||
191→ warn!("Shutdown channel disconnected");
|
||||
192→ break;
|
||||
193→ }
|
||||
194→ }
|
||||
195→ }
|
||||
196→ });
|
||||
197→
|
||||
198→ // Wait for shutdown
|
||||
199→ tokio::select! {
|
||||
200→ _ = shutdown_handle => {
|
||||
201→ info!("Service shutting down gracefully");
|
||||
202→ }
|
||||
203→ _ = ws_handle => {
|
||||
204→ error!("WebSocket task ended unexpectedly");
|
||||
205→ }
|
||||
206→ _ = metrics_handle => {
|
||||
207→ error!("Metrics task ended unexpectedly");
|
||||
208→ }
|
||||
209→ }
|
||||
210→
|
||||
211→ Ok::<(), anyhow::Error>(())
|
||||
212→ });
|
||||
213→
|
||||
214→ // Report that we're stopping
|
||||
215→ status_handle
|
||||
216→ .set_service_status(ServiceStatus {
|
||||
217→ service_type: ServiceType::OWN_PROCESS,
|
||||
218→ current_state: ServiceState::StopPending,
|
||||
219→ controls_accepted: ServiceControlAccept::empty(),
|
||||
220→ exit_code: ServiceExitCode::Win32(0),
|
||||
221→ checkpoint: 0,
|
||||
222→ wait_hint: Duration::from_secs(5),
|
||||
223→ process_id: None,
|
||||
224→ })
|
||||
225→ .ok();
|
||||
226→
|
||||
227→ // Report that we've stopped
|
||||
228→ status_handle
|
||||
229→ .set_service_status(ServiceStatus {
|
||||
230→ service_type: ServiceType::OWN_PROCESS,
|
||||
231→ current_state: ServiceState::Stopped,
|
||||
232→ controls_accepted: ServiceControlAccept::empty(),
|
||||
233→ exit_code: match &agent_result {
|
||||
234→ Ok(_) => ServiceExitCode::Win32(0),
|
||||
235→ Err(_) => ServiceExitCode::Win32(1),
|
||||
236→ },
|
||||
237→ checkpoint: 0,
|
||||
238→ wait_hint: Duration::default(),
|
||||
239→ process_id: None,
|
||||
240→ })
|
||||
241→ .ok();
|
||||
242→
|
||||
243→ agent_result
|
||||
244→ }
|
||||
245→
|
||||
246→ /// Known legacy service names to check and remove
|
||||
247→ const LEGACY_SERVICE_NAMES: &[&str] = &[
|
||||
248→ "GuruRMM-Agent", // NSSM-based service name
|
||||
249→ "gururmm-agent", // Alternative casing
|
||||
250→ ];
|
||||
251→
|
||||
252→ /// Detect and remove legacy service installations (e.g., NSSM-based)
|
||||
253→ fn cleanup_legacy_services() -> Result<()> {
|
||||
254→ let manager = match ServiceManager::local_computer(
|
||||
255→ None::<&str>,
|
||||
256→ ServiceManagerAccess::CONNECT,
|
||||
257→ ) {
|
||||
258→ Ok(m) => m,
|
||||
259→ Err(_) => return Ok(()), // Can't connect, skip legacy cleanup
|
||||
260→ };
|
||||
261→
|
||||
262→ for legacy_name in LEGACY_SERVICE_NAMES {
|
||||
263→ if let Ok(service) = manager.open_service(
|
||||
264→ *legacy_name,
|
||||
265→ ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
|
||||
266→ ) {
|
||||
267→ info!("Found legacy service '{}', removing...", legacy_name);
|
||||
268→
|
||||
269→ // Stop if running
|
||||
270→ if let Ok(status) = service.query_status() {
|
||||
271→ if status.current_state != ServiceState::Stopped {
|
||||
272→ info!("Stopping legacy service...");
|
||||
273→ let _ = service.stop();
|
||||
274→ std::thread::sleep(Duration::from_secs(3));
|
||||
275→ }
|
||||
276→ }
|
||||
277→
|
||||
278→ // Delete the service
|
||||
279→ match service.delete() {
|
||||
280→ Ok(_) => {
|
||||
281→ println!("** Removed legacy service: {}", legacy_name);
|
||||
282→ }
|
||||
283→ Err(e) => {
|
||||
284→ warn!("Failed to delete legacy service '{}': {}", legacy_name, e);
|
||||
285→ }
|
||||
286→ }
|
||||
287→ }
|
||||
288→ }
|
||||
289→
|
||||
290→ // Also check for NSSM in registry/service config
|
||||
291→ // NSSM services have specific registry keys under HKLM\SYSTEM\CurrentControlSet\Services\{name}\Parameters
|
||||
292→ for legacy_name in LEGACY_SERVICE_NAMES {
|
||||
293→ let params_key = format!(
|
||||
294→ r"SYSTEM\CurrentControlSet\Services\{}\Parameters",
|
||||
295→ legacy_name
|
||||
296→ );
|
||||
297→ // If this key exists, it was likely an NSSM service
|
||||
298→ if let Ok(output) = std::process::Command::new("reg")
|
||||
299→ .args(["query", &format!(r"HKLM\{}", params_key)])
|
||||
300→ .output()
|
||||
301→ {
|
||||
302→ if output.status.success() {
|
||||
303→ info!("Found NSSM registry keys for '{}', cleaning up...", legacy_name);
|
||||
304→ let _ = std::process::Command::new("reg")
|
||||
305→ .args(["delete", &format!(r"HKLM\{}", params_key), "/f"])
|
||||
306→ .output();
|
||||
307→ }
|
||||
308→ }
|
||||
309→ }
|
||||
310→
|
||||
311→ Ok(())
|
||||
312→ }
|
||||
313→
|
||||
314→ /// Install the agent as a Windows service using native APIs
|
||||
315→ pub fn install(
|
||||
316→ server_url: Option<String>,
|
||||
317→ api_key: Option<String>,
|
||||
318→ skip_legacy_check: bool,
|
||||
319→ ) -> Result<()> {
|
||||
320→ info!("Installing GuruRMM Agent as Windows service...");
|
||||
321→
|
||||
322→ // Clean up legacy installations unless skipped
|
||||
323→ if !skip_legacy_check {
|
||||
324→ info!("Checking for legacy service installations...");
|
||||
325→ if let Err(e) = cleanup_legacy_services() {
|
||||
326→ warn!("Legacy cleanup warning: {}", e);
|
||||
327→ }
|
||||
328→ }
|
||||
329→
|
||||
330→ // Get the current executable path
|
||||
331→ let current_exe =
|
||||
332→ std::env::current_exe().context("Failed to get current executable path")?;
|
||||
333→
|
||||
334→ let binary_dest = PathBuf::from(format!(r"{}\gururmm-agent.exe", INSTALL_DIR));
|
||||
335→ let config_dest = PathBuf::from(format!(r"{}\agent.toml", CONFIG_DIR));
|
||||
336→
|
||||
337→ // Create directories
|
||||
338→ info!("Creating directories...");
|
||||
339→ std::fs::create_dir_all(INSTALL_DIR).context("Failed to create install directory")?;
|
||||
340→ std::fs::create_dir_all(CONFIG_DIR).context("Failed to create config directory")?;
|
||||
341→
|
||||
342→ // Copy binary
|
||||
343→ info!("Copying binary to: {:?}", binary_dest);
|
||||
344→ std::fs::copy(¤t_exe, &binary_dest).context("Failed to copy binary")?;
|
||||
345→
|
||||
346→ // Handle configuration
|
||||
347→ let config_needs_manual_edit;
|
||||
348→ if !config_dest.exists() {
|
||||
349→ info!("Creating config: {:?}", config_dest);
|
||||
350→
|
||||
351→ // Start with sample config
|
||||
352→ let mut config = crate::config::AgentConfig::sample();
|
||||
353→
|
||||
354→ // Apply provided values
|
||||
355→ if let Some(url) = &server_url {
|
||||
356→ config.server.url = url.clone();
|
||||
357→ }
|
||||
358→ if let Some(key) = &api_key {
|
||||
359→ config.server.api_key = key.clone();
|
||||
360→ }
|
||||
361→
|
||||
362→ let toml_str = toml::to_string_pretty(&config)?;
|
||||
363→ std::fs::write(&config_dest, toml_str).context("Failed to write config file")?;
|
||||
364→
|
||||
365→ config_needs_manual_edit = server_url.is_none() || api_key.is_none();
|
||||
366→ } else {
|
||||
367→ info!("Config already exists: {:?}", config_dest);
|
||||
368→ config_needs_manual_edit = false;
|
||||
369→
|
||||
370→ // If server_url or api_key provided, update existing config
|
||||
371→ if server_url.is_some() || api_key.is_some() {
|
||||
372→ info!("Updating existing configuration...");
|
||||
373→ let config_content = std::fs::read_to_string(&config_dest)?;
|
||||
374→ let mut config: crate::config::AgentConfig = toml::from_str(&config_content)
|
||||
375→ .context("Failed to parse existing config")?;
|
||||
376→
|
||||
377→ if let Some(url) = &server_url {
|
||||
378→ config.server.url = url.clone();
|
||||
379→ }
|
||||
380→ if let Some(key) = &api_key {
|
||||
381→ config.server.api_key = key.clone();
|
||||
382→ }
|
||||
383→
|
||||
384→ let toml_str = toml::to_string_pretty(&config)?;
|
||||
385→ std::fs::write(&config_dest, toml_str)
|
||||
386→ .context("Failed to update config file")?;
|
||||
387→ }
|
||||
388→ }
|
||||
389→
|
||||
390→ // Open the service manager
|
||||
391→ let manager = ServiceManager::local_computer(
|
||||
392→ None::<&str>,
|
||||
393→ ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE,
|
||||
394→ )
|
||||
395→ .context("Failed to connect to Service Control Manager. Run as Administrator.")?;
|
||||
396→
|
||||
397→ // Check if service already exists
|
||||
398→ if let Ok(service) = manager.open_service(
|
||||
399→ SERVICE_NAME,
|
||||
400→ ServiceAccess::QUERY_STATUS | ServiceAccess::DELETE | ServiceAccess::STOP,
|
||||
401→ ) {
|
||||
402→ info!("Removing existing service...");
|
||||
403→
|
||||
404→ // Stop the service if running
|
||||
405→ if let Ok(status) = service.query_status() {
|
||||
406→ if status.current_state != ServiceState::Stopped {
|
||||
407→ let _ = service.stop();
|
||||
408→ std::thread::sleep(Duration::from_secs(2));
|
||||
409→ }
|
||||
410→ }
|
||||
411→
|
||||
412→ // Delete the service
|
||||
413→ service.delete().context("Failed to delete existing service")?;
|
||||
414→ drop(service);
|
||||
415→
|
||||
416→ // Wait for deletion to complete
|
||||
417→ std::thread::sleep(Duration::from_secs(2));
|
||||
418→ }
|
||||
419→
|
||||
420→ // Create the service
|
||||
421→ // The service binary is called with "service" subcommand when started by SCM
|
||||
422→ let service_binary_path = format!(r#""{}" service"#, binary_dest.display());
|
||||
423→
|
||||
424→ info!("Creating service with path: {}", service_binary_path);
|
||||
425→
|
||||
426→ let service_info = ServiceInfo {
|
||||
427→ name: OsString::from(SERVICE_NAME),
|
||||
428→ display_name: OsString::from(SERVICE_DISPLAY_NAME),
|
||||
429→ service_type: ServiceType::OWN_PROCESS,
|
||||
430→ start_type: ServiceStartType::AutoStart,
|
||||
431→ error_control: ServiceErrorControl::Normal,
|
||||
432→ executable_path: binary_dest.clone(),
|
||||
433→ launch_arguments: vec![OsString::from("service")],
|
||||
434→ dependencies: vec![],
|
||||
435→ account_name: None, // LocalSystem
|
||||
436→ account_password: None,
|
||||
437→ };
|
||||
438→
|
||||
439→ let service = manager
|
||||
440→ .create_service(&service_info, ServiceAccess::CHANGE_CONFIG | ServiceAccess::START)
|
||||
441→ .context("Failed to create service")?;
|
||||
442→
|
||||
443→ // Set description
|
||||
444→ service
|
||||
445→ .set_description(SERVICE_DESCRIPTION)
|
||||
446→ .context("Failed to set service description")?;
|
||||
447→
|
||||
448→ // Configure recovery options using sc.exe (windows-service crate doesn't support this directly)
|
||||
449→ info!("Configuring recovery options...");
|
||||
450→ let _ = std::process::Command::new("sc")
|
||||
451→ .args([
|
||||
452→ "failure",
|
||||
453→ SERVICE_NAME,
|
||||
454→ "reset=86400",
|
||||
455→ "actions=restart/60000/restart/60000/restart/60000",
|
||||
456→ ])
|
||||
457→ .output();
|
||||
458→
|
||||
459→ println!("\n** GuruRMM Agent installed successfully!");
|
||||
460→ println!("\nInstalled files:");
|
||||
461→ println!(" Binary: {:?}", binary_dest);
|
||||
462→ println!(" Config: {:?}", config_dest);
|
||||
463→
|
||||
464→ if config_needs_manual_edit {
|
||||
465→ println!("\n** IMPORTANT: Edit {:?} with your server URL and API key!", config_dest);
|
||||
466→ println!("\nNext steps:");
|
||||
467→ println!(" 1. Edit {:?} with your server URL and API key", config_dest);
|
||||
468→ println!(" 2. Start the service:");
|
||||
469→ println!(" gururmm-agent start");
|
||||
470→ println!(" Or: sc start {}", SERVICE_NAME);
|
||||
471→ } else {
|
||||
472→ println!("\nStarting service...");
|
||||
473→ if let Err(e) = start() {
|
||||
474→ println!("** Failed to start service: {}. Start manually with:", e);
|
||||
475→ println!(" gururmm-agent start");
|
||||
476→ } else {
|
||||
477→ println!("** Service started successfully!");
|
||||
478→ }
|
||||
479→ }
|
||||
480→
|
||||
481→ println!("\nUseful commands:");
|
||||
482→ println!(" Status: gururmm-agent status");
|
||||
483→ println!(" Stop: gururmm-agent stop");
|
||||
484→ println!(" Start: gururmm-agent start");
|
||||
485→
|
||||
486→ Ok(())
|
||||
487→ }
|
||||
488→
|
||||
489→ /// Uninstall the Windows service
|
||||
490→ pub fn uninstall() -> Result<()> {
|
||||
491→ info!("Uninstalling GuruRMM Agent...");
|
||||
492→
|
||||
493→ let binary_path = PathBuf::from(format!(r"{}\gururmm-agent.exe", INSTALL_DIR));
|
||||
494→
|
||||
495→ // Open the service manager
|
||||
496→ let manager = ServiceManager::local_computer(
|
||||
497→ None::<&str>,
|
||||
498→ ServiceManagerAccess::CONNECT,
|
||||
499→ )
|
||||
500→ .context("Failed to connect to Service Control Manager. Run as Administrator.")?;
|
||||
501→
|
||||
502→ // Open the service
|
||||
503→ match manager.open_service(
|
||||
504→ SERVICE_NAME,
|
||||
505→ ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
|
||||
506→ ) {
|
||||
507→ Ok(service) => {
|
||||
508→ // Stop if running
|
||||
509→ if let Ok(status) = service.query_status() {
|
||||
510→ if status.current_state != ServiceState::Stopped {
|
||||
511→ info!("Stopping service...");
|
||||
512→ let _ = service.stop();
|
||||
513→ std::thread::sleep(Duration::from_secs(3));
|
||||
514→ }
|
||||
515→ }
|
||||
516→
|
||||
517→ // Delete the service
|
||||
518→ info!("Deleting service...");
|
||||
519→ service.delete().context("Failed to delete service")?;
|
||||
520→ }
|
||||
521→ Err(_) => {
|
||||
522→ warn!("Service was not installed");
|
||||
523→ }
|
||||
524→ }
|
||||
525→
|
||||
526→ // Remove binary
|
||||
527→ if binary_path.exists() {
|
||||
528→ info!("Removing binary: {:?}", binary_path);
|
||||
529→ // Wait a bit for service to fully stop
|
||||
530→ std::thread::sleep(Duration::from_secs(1));
|
||||
531→ if let Err(e) = std::fs::remove_file(&binary_path) {
|
||||
532→ warn!("Failed to remove binary (may be in use): {}", e);
|
||||
533→ }
|
||||
534→ }
|
||||
535→
|
||||
536→ // Remove install directory if empty
|
||||
537→ let _ = std::fs::remove_dir(INSTALL_DIR);
|
||||
538→
|
||||
539→ println!("\n** GuruRMM Agent uninstalled successfully!");
|
||||
540→ println!(
|
||||
541→ "\nNote: Config directory {:?} was preserved.",
|
||||
542→ CONFIG_DIR
|
||||
543→ );
|
||||
544→ println!("Remove it manually if no longer needed.");
|
||||
545→
|
||||
546→ Ok(())
|
||||
547→ }
|
||||
548→
|
||||
549→ /// Start the installed service
|
||||
550→ pub fn start() -> Result<()> {
|
||||
551→ info!("Starting GuruRMM Agent service...");
|
||||
552→
|
||||
553→ let manager = ServiceManager::local_computer(
|
||||
554→ None::<&str>,
|
||||
555→ ServiceManagerAccess::CONNECT,
|
||||
556→ )
|
||||
557→ .context("Failed to connect to Service Control Manager")?;
|
||||
558→
|
||||
559→ let service = manager
|
||||
560→ .open_service(SERVICE_NAME, ServiceAccess::START | ServiceAccess::QUERY_STATUS)
|
||||
561→ .context("Failed to open service. Is it installed?")?;
|
||||
562→
|
||||
563→ service
|
||||
564→ .start::<String>(&[])
|
||||
565→ .context("Failed to start service")?;
|
||||
566→
|
||||
567→ // Wait briefly and check status
|
||||
568→ std::thread::sleep(Duration::from_secs(2));
|
||||
569→
|
||||
570→ let status = service.query_status()?;
|
||||
571→ match status.current_state {
|
||||
572→ ServiceState::Running => {
|
||||
573→ println!("** Service started successfully");
|
||||
574→ println!("Check status: gururmm-agent status");
|
||||
575→ }
|
||||
576→ ServiceState::StartPending => {
|
||||
577→ println!("** Service is starting...");
|
||||
578→ println!("Check status: gururmm-agent status");
|
||||
579→ }
|
||||
580→ other => {
|
||||
581→ println!("Service state: {:?}", other);
|
||||
582→ }
|
||||
583→ }
|
||||
584→
|
||||
585→ Ok(())
|
||||
586→ }
|
||||
587→
|
||||
588→ /// Stop the installed service
|
||||
589→ pub fn stop() -> Result<()> {
|
||||
590→ info!("Stopping GuruRMM Agent service...");
|
||||
591→
|
||||
592→ let manager = ServiceManager::local_computer(
|
||||
593→ None::<&str>,
|
||||
594→ ServiceManagerAccess::CONNECT,
|
||||
595→ )
|
||||
596→ .context("Failed to connect to Service Control Manager")?;
|
||||
597→
|
||||
598→ let service = manager
|
||||
599→ .open_service(SERVICE_NAME, ServiceAccess::STOP | ServiceAccess::QUERY_STATUS)
|
||||
600→ .context("Failed to open service. Is it installed?")?;
|
||||
601→
|
||||
602→ service.stop().context("Failed to stop service")?;
|
||||
603→
|
||||
604→ // Wait and verify
|
||||
605→ std::thread::sleep(Duration::from_secs(2));
|
||||
606→
|
||||
607→ let status = service.query_status()?;
|
||||
608→ match status.current_state {
|
||||
609→ ServiceState::Stopped => {
|
||||
610→ println!("** Service stopped successfully");
|
||||
611→ }
|
||||
612→ ServiceState::StopPending => {
|
||||
613→ println!("** Service is stopping...");
|
||||
614→ }
|
||||
615→ other => {
|
||||
616→ println!("Service state: {:?}", other);
|
||||
617→ }
|
||||
618→ }
|
||||
619→
|
||||
620→ Ok(())
|
||||
621→ }
|
||||
622→
|
||||
623→ /// Query service status
|
||||
624→ pub fn status() -> Result<()> {
|
||||
625→ let manager = ServiceManager::local_computer(
|
||||
626→ None::<&str>,
|
||||
627→ ServiceManagerAccess::CONNECT,
|
||||
628→ )
|
||||
629→ .context("Failed to connect to Service Control Manager")?;
|
||||
630→
|
||||
631→ match manager.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS) {
|
||||
632→ Ok(service) => {
|
||||
633→ let status = service.query_status()?;
|
||||
634→ println!("GuruRMM Agent Service Status");
|
||||
635→ println!("============================");
|
||||
636→ println!("Service Name: {}", SERVICE_NAME);
|
||||
637→ println!("Display Name: {}", SERVICE_DISPLAY_NAME);
|
||||
638→ println!("State: {:?}", status.current_state);
|
||||
639→ println!(
|
||||
640→ "Binary: {}\\gururmm-agent.exe",
|
||||
641→ INSTALL_DIR
|
||||
642→ );
|
||||
643→ println!("Config: {}\\agent.toml", CONFIG_DIR);
|
||||
644→ }
|
||||
645→ Err(_) => {
|
||||
646→ println!("GuruRMM Agent Service Status");
|
||||
647→ println!("============================");
|
||||
648→ println!("Status: NOT INSTALLED");
|
||||
649→ println!("\nTo install: gururmm-agent install");
|
||||
650→ }
|
||||
651→ }
|
||||
652→
|
||||
653→ Ok(())
|
||||
654→ }
|
||||
655→}
|
||||
656→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1 @@
|
||||
File has been unexpectedly modified. Read it again before attempting to write it.
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,12 @@
|
||||
The file C:\Users\MikeSwanson\Claude\shared-data\credentials.md has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
61→ 1. Build: `cargo build --release --target x86_64-unknown-linux-gnu -p <package>`
|
||||
62→ 2. Rename old: `mv target/release/binary target/release/binary.old`
|
||||
63→ 3. Copy new: `cp target/x86_64.../release/binary target/release/binary`
|
||||
64→ 4. Kill old: `pkill -f binary.old` (systemd auto-restarts)
|
||||
65→- **GuruConnect:** Static files in /home/guru/guru-connect/server/static/
|
||||
66→- **GuruConnect Startup:** `~/guru-connect/start-server.sh` (ALWAYS use this, kills old process and uses correct binary path)
|
||||
67→- **GuruConnect Binary:** /home/guru/guru-connect/target/x86_64-unknown-linux-gnu/release/guruconnect-server
|
||||
68→
|
||||
69→---
|
||||
70→
|
||||
71→## Services - Web Applications
|
||||
@@ -0,0 +1,363 @@
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: C:\Users\MikeSwanson\Claude\guru-connect\agent\Cargo.toml
|
||||
workspace: C:\Users\MikeSwanson\Claude\guru-connect\Cargo.toml
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: C:\Users\MikeSwanson\Claude\guru-connect\server\Cargo.toml
|
||||
workspace: C:\Users\MikeSwanson\Claude\guru-connect\Cargo.toml
|
||||
Downloading crates ...
|
||||
Downloaded mime_guess v2.0.5
|
||||
Downloaded http-range-header v0.4.2
|
||||
Downloaded unicase v2.8.1
|
||||
Compiling serde_core v1.0.228
|
||||
Compiling serde v1.0.228
|
||||
Compiling icu_properties_data v2.1.2
|
||||
Compiling icu_normalizer_data v2.1.1
|
||||
Compiling stable_deref_trait v1.2.1
|
||||
Compiling futures-core v0.3.31
|
||||
Compiling windows-sys v0.61.2
|
||||
Compiling windows_x86_64_msvc v0.48.5
|
||||
Compiling subtle v2.6.1
|
||||
Compiling typenum v1.19.0
|
||||
Compiling serde_json v1.0.145
|
||||
Compiling futures-sink v0.3.31
|
||||
Compiling memchr v2.7.6
|
||||
Compiling generic-array v0.14.7
|
||||
Compiling writeable v0.6.2
|
||||
Compiling syn v2.0.111
|
||||
Compiling litemap v0.8.1
|
||||
Compiling bytes v1.11.0
|
||||
Compiling windows_x86_64_msvc v0.53.1
|
||||
Compiling num-traits v0.2.19
|
||||
Compiling digest v0.10.7
|
||||
Compiling pin-project-lite v0.2.16
|
||||
Compiling crossbeam-utils v0.8.21
|
||||
Compiling futures-io v0.3.31
|
||||
Compiling zerocopy v0.8.31
|
||||
Compiling parking_lot_core v0.9.12
|
||||
Compiling percent-encoding v2.3.2
|
||||
Compiling scopeguard v1.2.0
|
||||
Compiling mio v1.1.1
|
||||
Compiling getrandom v0.2.16
|
||||
Compiling synstructure v0.13.2
|
||||
Compiling serde_derive v1.0.228
|
||||
Compiling zerovec-derive v0.11.2
|
||||
Compiling displaydoc v0.2.5
|
||||
Compiling tracing-attributes v0.1.31
|
||||
Compiling tokio-macros v2.6.0
|
||||
Compiling thiserror-impl v2.0.17
|
||||
Compiling windows-targets v0.53.5
|
||||
Compiling futures-macro v0.3.31
|
||||
Compiling lock_api v0.4.14
|
||||
Compiling crypto-common v0.1.7
|
||||
Compiling block-buffer v0.10.4
|
||||
Compiling zerofrom-derive v0.1.6
|
||||
Compiling yoke-derive v0.8.1
|
||||
Compiling windows-sys v0.60.2
|
||||
Compiling tinyvec_macros v0.1.1
|
||||
Compiling foldhash v0.1.5
|
||||
Compiling allocator-api2 v0.2.21
|
||||
Compiling utf8_iter v1.0.4
|
||||
Compiling windows-targets v0.48.5
|
||||
Compiling concurrent-queue v2.5.0
|
||||
Compiling tinyvec v1.10.0
|
||||
Compiling form_urlencoded v1.2.2
|
||||
Compiling rand_core v0.6.4
|
||||
Compiling futures-util v0.3.31
|
||||
Compiling tracing v0.1.44
|
||||
Compiling http-body v1.0.1
|
||||
Compiling tracing-core v0.1.36
|
||||
Compiling thiserror v2.0.17
|
||||
Compiling parking v2.2.1
|
||||
Compiling pin-utils v0.1.0
|
||||
Compiling cpufeatures v0.2.17
|
||||
Compiling hashbrown v0.15.5
|
||||
Compiling ppv-lite86 v0.2.21
|
||||
Compiling itoa v1.0.16
|
||||
Compiling ryu v1.0.21
|
||||
Compiling crc-catalog v2.4.0
|
||||
Compiling base64 v0.22.1
|
||||
Compiling futures-task v0.3.31
|
||||
Compiling socket2 v0.6.1
|
||||
Compiling zerofrom v0.1.6
|
||||
Compiling slab v0.4.11
|
||||
Compiling cc v1.2.50
|
||||
Compiling unicode-normalization v0.1.25
|
||||
Compiling sha2 v0.10.9
|
||||
Compiling event-listener v5.4.1
|
||||
Compiling chrono v0.4.42
|
||||
Compiling crc v3.4.0
|
||||
Compiling rand_chacha v0.3.1
|
||||
Compiling hmac v0.12.1
|
||||
Compiling windows-sys v0.48.0
|
||||
Compiling crossbeam-queue v0.3.12
|
||||
Compiling yoke v0.8.1
|
||||
Compiling tokio v1.48.0
|
||||
Compiling hashlink v0.10.0
|
||||
Compiling home v0.5.12
|
||||
Compiling futures-channel v0.3.31
|
||||
Compiling getrandom v0.3.4
|
||||
Compiling unicode-bidi v0.3.18
|
||||
Compiling smallvec v1.15.1
|
||||
Compiling either v1.15.0
|
||||
Compiling uuid v1.19.0
|
||||
Compiling tower-service v0.3.3
|
||||
Compiling simd-adler32 v0.3.8
|
||||
Compiling zerovec v0.11.5
|
||||
Compiling zerotrie v0.2.3
|
||||
Compiling unicode-properties v0.1.4
|
||||
Compiling ring v0.17.14
|
||||
Compiling etcetera v0.8.0
|
||||
Compiling hkdf v0.12.4
|
||||
Compiling itertools v0.14.0
|
||||
Compiling rand v0.8.5
|
||||
Compiling miniz_oxide v0.8.9
|
||||
Compiling atoi v2.0.0
|
||||
Compiling md-5 v0.10.6
|
||||
Compiling stringprep v0.1.5
|
||||
Compiling thiserror-impl v1.0.69
|
||||
Compiling tower-layer v0.3.3
|
||||
Compiling parking_lot v0.12.5
|
||||
Compiling byteorder v1.5.0
|
||||
Compiling num-conv v0.1.0
|
||||
Compiling dotenvy v0.15.7
|
||||
Compiling tinystr v0.8.2
|
||||
Compiling potential_utf v0.1.4
|
||||
Compiling mime v0.3.17
|
||||
Compiling unicase v2.8.1
|
||||
Compiling time-core v0.1.6
|
||||
Compiling tokio-stream v0.1.17
|
||||
Compiling powerfmt v0.2.0
|
||||
Compiling whoami v1.6.1
|
||||
Compiling rustversion v1.0.22
|
||||
Compiling bitflags v2.10.0
|
||||
Compiling futures-intrusive v0.5.0
|
||||
Compiling httpdate v1.0.3
|
||||
Compiling icu_locale_core v2.1.1
|
||||
Compiling icu_collections v2.1.1
|
||||
Compiling hex v0.4.3
|
||||
Compiling mime_guess v2.0.5
|
||||
Compiling time-macros v0.2.24
|
||||
Compiling prost-derive v0.13.5
|
||||
Compiling deranged v0.5.5
|
||||
Compiling flate2 v1.1.5
|
||||
Compiling tempfile v3.23.0
|
||||
Compiling thiserror v1.0.69
|
||||
Compiling prettyplease v0.2.37
|
||||
Compiling http-body-util v0.1.3
|
||||
Compiling num-integer v0.1.46
|
||||
Compiling sha1 v0.10.6
|
||||
Compiling atomic-waker v1.1.2
|
||||
Compiling icu_provider v2.1.1
|
||||
Compiling compression-core v0.4.31
|
||||
Compiling prost v0.13.5
|
||||
Compiling sync_wrapper v1.0.2
|
||||
Compiling hyper v1.8.1
|
||||
Compiling num-bigint v0.4.6
|
||||
Compiling tungstenite v0.24.0
|
||||
Compiling toml_datetime v0.6.3
|
||||
Compiling time v0.3.44
|
||||
Compiling compression-codecs v0.4.35
|
||||
Compiling serde_spanned v0.6.9
|
||||
Compiling async-trait v0.1.89
|
||||
Compiling icu_properties v2.1.2
|
||||
Compiling icu_normalizer v2.1.1
|
||||
Compiling prost-types v0.13.5
|
||||
Compiling base64ct v1.8.1
|
||||
Compiling toml_edit v0.20.2
|
||||
Compiling async-compression v0.4.36
|
||||
Compiling tokio-tungstenite v0.24.0
|
||||
Compiling tower v0.5.2
|
||||
Compiling tokio-util v0.7.17
|
||||
Compiling serde_urlencoded v0.7.1
|
||||
Compiling pem v3.0.6
|
||||
Compiling hyper-util v0.1.19
|
||||
Compiling prost-build v0.13.5
|
||||
Compiling axum-core v0.4.5
|
||||
Compiling idna_adapter v1.2.1
|
||||
Compiling idna v1.1.0
|
||||
Compiling password-hash v0.5.0
|
||||
Compiling serde_path_to_error v0.1.20
|
||||
Compiling axum-macros v0.4.2
|
||||
Compiling nu-ansi-term v0.50.3
|
||||
Compiling blake2 v0.10.6
|
||||
Compiling matchit v0.7.3
|
||||
Compiling http-range-header v0.4.2
|
||||
Compiling url v2.5.7
|
||||
Compiling tower-http v0.6.8
|
||||
Compiling simple_asn1 v0.6.3
|
||||
Compiling argon2 v0.5.3
|
||||
Compiling tracing-subscriber v0.3.22
|
||||
Compiling toml v0.8.2
|
||||
Compiling guruconnect-server v0.1.0 (C:\Users\MikeSwanson\Claude\guru-connect\server)
|
||||
Compiling sqlx-core v0.8.6
|
||||
Compiling jsonwebtoken v9.3.1
|
||||
Compiling axum v0.7.9
|
||||
Compiling sqlx-postgres v0.8.6
|
||||
Compiling sqlx-macros-core v0.8.6
|
||||
Compiling sqlx-macros v0.8.6
|
||||
Compiling sqlx v0.8.6
|
||||
warning: unused variable: `config`
|
||||
--> server\src\main.rs:54:9
|
||||
|
|
||||
54 | let config = config::Config::load()?;
|
||||
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_config`
|
||||
|
|
||||
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: struct `ValidateParams` is never constructed
|
||||
--> server\src\main.rs:138:8
|
||||
|
|
||||
138 | struct ValidateParams {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: fields `listen_addr`, `database_url`, `jwt_secret`, and `debug` are never read
|
||||
--> server\src\config.rs:10:9
|
||||
|
|
||||
8 | pub struct Config {
|
||||
| ------ fields in this struct
|
||||
9 | /// Address to listen on (e.g., "0.0.0.0:8080")
|
||||
10 | pub listen_addr: String,
|
||||
| ^^^^^^^^^^^
|
||||
...
|
||||
13 | pub database_url: Option<String>,
|
||||
| ^^^^^^^^^^^^
|
||||
...
|
||||
16 | pub jwt_secret: Option<String>,
|
||||
| ^^^^^^^^^^
|
||||
...
|
||||
19 | pub debug: bool,
|
||||
| ^^^^^
|
||||
|
|
||||
= note: `Config` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
||||
|
||||
warning: constant `HEARTBEAT_TIMEOUT_SECS` is never used
|
||||
--> server\src\session\mod.rs:22:7
|
||||
|
|
||||
22 | const HEARTBEAT_TIMEOUT_SECS: u64 = 90;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `input_rx` is never read
|
||||
--> server\src\session\mod.rs:58:5
|
||||
|
|
||||
52 | struct SessionData {
|
||||
| ----------- field in this struct
|
||||
...
|
||||
58 | input_rx: Option<InputReceiver>,
|
||||
| ^^^^^^^^
|
||||
|
||||
warning: methods `is_session_timed_out`, `get_timed_out_sessions`, `get_session_by_agent`, and `remove_session` are never used
|
||||
--> server\src\session\mod.rs:185:18
|
||||
|
|
||||
72 | impl SessionManager {
|
||||
| ------------------- methods in this implementation
|
||||
...
|
||||
185 | pub async fn is_session_timed_out(&self, session_id: SessionId) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
195 | pub async fn get_timed_out_sessions(&self) -> Vec<SessionId> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
205 | pub async fn get_session_by_agent(&self, agent_id: &str) -> Option<Session> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
317 | pub async fn remove_session(&self, session_id: SessionId) {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `AuthenticatedUser` is never constructed
|
||||
--> server\src\auth\mod.rs:13:12
|
||||
|
|
||||
13 | pub struct AuthenticatedUser {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `AuthenticatedAgent` is never constructed
|
||||
--> server\src\auth\mod.rs:21:12
|
||||
|
|
||||
21 | pub struct AuthenticatedAgent {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `validate_agent_key` is never used
|
||||
--> server\src\auth\mod.rs:54:8
|
||||
|
|
||||
54 | pub fn validate_agent_key(_api_key: &str) -> Option<AuthenticatedAgent> {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `list_sessions` is never used
|
||||
--> server\src\api\mod.rs:51:14
|
||||
|
|
||||
51 | pub async fn list_sessions(
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_session` is never used
|
||||
--> server\src\api\mod.rs:59:14
|
||||
|
|
||||
59 | pub async fn get_session(
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: struct `Database` is never constructed
|
||||
--> server\src\db\mod.rs:10:12
|
||||
|
|
||||
10 | pub struct Database {
|
||||
| ^^^^^^^^
|
||||
|
||||
warning: associated function `init` is never used
|
||||
--> server\src\db\mod.rs:17:18
|
||||
|
|
||||
15 | impl Database {
|
||||
| ------------- associated function in this implementation
|
||||
16 | /// Initialize database connection
|
||||
17 | pub async fn init(_database_url: &str) -> Result<Self> {
|
||||
| ^^^^
|
||||
|
||||
warning: struct `SessionEvent` is never constructed
|
||||
--> server\src\db\mod.rs:25:12
|
||||
|
|
||||
25 | pub struct SessionEvent {
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
warning: enum `SessionEventType` is never used
|
||||
--> server\src\db\mod.rs:32:10
|
||||
|
|
||||
32 | pub enum SessionEventType {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: method `log_session_event` is never used
|
||||
--> server\src\db\mod.rs:41:18
|
||||
|
|
||||
39 | impl Database {
|
||||
| ------------- method in this implementation
|
||||
40 | /// Log a session event (placeholder)
|
||||
41 | pub async fn log_session_event(&self, _event: SessionEvent) -> Result<()> {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `technician_id` is never read
|
||||
--> server\src\support_codes.rs:39:9
|
||||
|
|
||||
38 | pub struct CreateCodeRequest {
|
||||
| ----------------- field in this struct
|
||||
39 | pub technician_id: Option<String>,
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `CreateCodeRequest` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
||||
|
||||
warning: methods `get_code`, `is_valid_for_connection`, `list_codes`, and `get_by_session` are never used
|
||||
--> server\src\support_codes.rs:166:18
|
||||
|
|
||||
60 | impl SupportCodeManager {
|
||||
| ----------------------- methods in this implementation
|
||||
...
|
||||
166 | pub async fn get_code(&self, code: &str) -> Option<SupportCode> {
|
||||
| ^^^^^^^^
|
||||
...
|
||||
198 | pub async fn is_valid_for_connection(&self, code: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
204 | pub async fn list_codes(&self) -> Vec<SupportCode> {
|
||||
| ^^^^^^^^^^
|
||||
...
|
||||
219 | pub async fn get_by_session(&self, session_id: Uuid) -> Option<SupportCode> {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: `guruconnect-server` (bin "guruconnect-server") generated 18 warnings (run `cargo fix --bin "guruconnect-server" -p guruconnect-server` to apply 1 suggestion)
|
||||
Finished `release` profile [optimized] target(s) in 1m 03s
|
||||
@@ -0,0 +1,3 @@
|
||||
Login: SUCCESS
|
||||
User: admin
|
||||
Role: admin
|
||||
@@ -0,0 +1 @@
|
||||
DELETE 1
|
||||
@@ -0,0 +1,630 @@
|
||||
1→# Credentials & Authorization Reference
|
||||
2→**Last Updated:** 2025-12-16
|
||||
3→**Purpose:** Centralized credentials for Claude Code context recovery across all machines
|
||||
4→
|
||||
5→---
|
||||
6→
|
||||
7→## Infrastructure - SSH Access
|
||||
8→
|
||||
9→### Jupiter (Unraid Primary)
|
||||
10→- **Host:** 172.16.3.20
|
||||
11→- **User:** root
|
||||
12→- **Port:** 22
|
||||
13→- **Password:** Th1nk3r^99##
|
||||
14→- **WebUI Password:** Th1nk3r^99##
|
||||
15→- **Role:** Primary container host (Gitea, NPM, GuruRMM, media)
|
||||
16→- **iDRAC IP:** 172.16.1.73 (DHCP)
|
||||
17→- **iDRAC User:** root
|
||||
18→- **iDRAC Password:** Window123!@#-idrac
|
||||
19→- **iDRAC SSH:** Enabled (port 22)
|
||||
20→- **IPMI Key:** All zeros
|
||||
21→
|
||||
22→### Saturn (Unraid Secondary)
|
||||
23→- **Host:** 172.16.3.21
|
||||
24→- **User:** root
|
||||
25→- **Port:** 22
|
||||
26→- **Password:** r3tr0gradE99
|
||||
27→- **Role:** Migration source, being consolidated to Jupiter
|
||||
28→
|
||||
29→### pfSense (Firewall)
|
||||
30→- **Host:** 172.16.0.1
|
||||
31→- **User:** admin
|
||||
32→- **Port:** 2248
|
||||
33→- **Password:** r3tr0gradE99!!
|
||||
34→- **Role:** Firewall, Tailscale gateway
|
||||
35→- **Tailscale IP:** 100.79.69.82 (pfsense-1)
|
||||
36→
|
||||
37→### OwnCloud VM (on Jupiter)
|
||||
38→- **Host:** 172.16.3.22
|
||||
39→- **Hostname:** cloud.acghosting.com
|
||||
40→- **User:** root
|
||||
41→- **Port:** 22
|
||||
42→- **Password:** Paper123!@#-unifi!
|
||||
43→- **OS:** Rocky Linux 9.6
|
||||
44→- **Role:** OwnCloud file sync server
|
||||
45→- **Services:** Apache, MariaDB, PHP-FPM, Redis, Datto RMM agents
|
||||
46→- **Storage:** SMB mount from Jupiter (/mnt/user/OwnCloud)
|
||||
47→- **Note:** Jupiter has SSH key auth configured
|
||||
48→
|
||||
49→### GuruRMM Build Server
|
||||
50→- **Host:** 172.16.3.30
|
||||
51→- **Hostname:** gururmm
|
||||
52→- **User:** guru
|
||||
53→- **Port:** 22
|
||||
54→- **Password:** Gptf*77ttb123!@#-rmm
|
||||
55→- **Sudo Password:** Gptf*77ttb123!@#-rmm (special chars cause issues with sudo -S)
|
||||
56→- **OS:** Ubuntu 22.04
|
||||
57→- **Role:** GuruRMM/GuruConnect dedicated server (API, DB, Dashboard, Downloads, GuruConnect relay)
|
||||
58→- **Services:** nginx, PostgreSQL, gururmm-server, gururmm-agent, guruconnect-server
|
||||
59→- **SSH Key Auth:** ✅ Working from Windows/WSL (ssh guru@172.16.3.30)
|
||||
60→- **Service Restart Method:** Services run as guru user, so `pkill` works without sudo. Deploy pattern:
|
||||
61→ 1. Build: `cargo build --release --target x86_64-unknown-linux-gnu -p <package>`
|
||||
62→ 2. Rename old: `mv target/release/binary target/release/binary.old`
|
||||
63→ 3. Copy new: `cp target/x86_64.../release/binary target/release/binary`
|
||||
64→ 4. Kill old: `pkill -f binary.old` (systemd auto-restarts)
|
||||
65→- **GuruConnect:** Static files in /home/guru/guru-connect/server/static/, binary at /home/guru/guru-connect/target/release/guruconnect-server
|
||||
66→
|
||||
67→---
|
||||
68→
|
||||
69→## Services - Web Applications
|
||||
70→
|
||||
71→### Gitea (Git Server)
|
||||
72→- **URL:** https://git.azcomputerguru.com/
|
||||
73→- **Internal:** http://172.16.3.20:3000
|
||||
74→- **SSH:** ssh://git@172.16.3.20:2222
|
||||
75→- **User:** mike@azcomputerguru.com
|
||||
76→- **Password:** Window123!@#-git
|
||||
77→- **API Token:** 9b1da4b79a38ef782268341d25a4b6880572063f
|
||||
78→
|
||||
79→### NPM (Nginx Proxy Manager)
|
||||
80→- **Admin URL:** http://172.16.3.20:7818
|
||||
81→- **HTTP Port:** 1880
|
||||
82→- **HTTPS Port:** 18443
|
||||
83→- **User:** mike@azcomputerguru.com
|
||||
84→- **Password:** Paper123!@#-unifi
|
||||
85→
|
||||
86→### Cloudflare
|
||||
87→- **API Token (Full DNS):** DRRGkHS33pxAUjQfRDzDeVPtt6wwUU6FwtXqOzNj
|
||||
88→- **API Token (Legacy/Limited):** U1UTbBOWA4a69eWEBiqIbYh0etCGzrpTU4XaKp7w
|
||||
89→- **Permissions:** Zone:Read, Zone:Edit, DNS:Read, DNS:Edit
|
||||
90→- **Used for:** DNS management, WHM plugin, cf-dns CLI
|
||||
91→- **Domain:** azcomputerguru.com
|
||||
92→- **Notes:** New full-access token added 2025-12-19
|
||||
93→
|
||||
94→---
|
||||
95→
|
||||
96→## Projects - GuruRMM
|
||||
97→
|
||||
98→### Dashboard/API Login
|
||||
99→- **Email:** admin@azcomputerguru.com
|
||||
100→- **Password:** GuruRMM2025
|
||||
101→- **Role:** admin
|
||||
102→
|
||||
103→### Database (PostgreSQL)
|
||||
104→- **Host:** gururmm-db container (172.16.3.20)
|
||||
105→- **Database:** gururmm
|
||||
106→- **User:** gururmm
|
||||
107→- **Password:** 43617ebf7eb242e814ca9988cc4df5ad
|
||||
108→
|
||||
109→---
|
||||
110→
|
||||
111→## Projects - GuruConnect
|
||||
112→
|
||||
113→### Database (PostgreSQL on build server)
|
||||
114→- **Host:** localhost (172.16.3.30)
|
||||
115→- **Port:** 5432
|
||||
116→- **Database:** guruconnect
|
||||
117→- **User:** guruconnect
|
||||
118→- **Password:** gc_a7f82d1e4b9c3f60
|
||||
119→- **DATABASE_URL:** `postgres://guruconnect:gc_a7f82d1e4b9c3f60@localhost:5432/guruconnect`
|
||||
120→- **Created:** 2025-12-28
|
||||
121→
|
||||
122→---
|
||||
123→
|
||||
124→## Projects - GuruRMM (continued)
|
||||
125→
|
||||
126→### API Server
|
||||
127→- **External URL:** https://rmm-api.azcomputerguru.com
|
||||
128→- **Internal URL:** http://172.16.3.20:3001
|
||||
129→- **JWT Secret:** ZNzGxghru2XUdBVlaf2G2L1YUBVcl5xH0lr/Gpf/QmE=
|
||||
130→
|
||||
131→### Microsoft Entra ID (SSO)
|
||||
132→- **App Name:** GuruRMM Dashboard
|
||||
133→- **App ID (Client ID):** 18a15f5d-7ab8-46f4-8566-d7b5436b84b6
|
||||
134→- **Object ID:** 34c80aa8-385a-4bea-af85-f8bf67decc8f
|
||||
135→- **Client Secret:** gOz8Q~J.oz7KnUIEpzmHOyJ6GEzYNecGRl-Pbc9w
|
||||
136→- **Secret Expires:** 2026-12-21
|
||||
137→- **Sign-in Audience:** Multi-tenant (any Azure AD org)
|
||||
138→- **Redirect URIs:** https://rmm.azcomputerguru.com/auth/callback, http://localhost:5173/auth/callback
|
||||
139→- **API Permissions:** openid, email, profile
|
||||
140→- **Notes:** Created 2025-12-21 for GuruRMM SSO
|
||||
141→
|
||||
142→### CI/CD (Build Automation)
|
||||
143→- **Webhook URL:** http://172.16.3.30/webhook/build
|
||||
144→- **Webhook Secret:** gururmm-build-secret
|
||||
145→- **Build Script:** /opt/gururmm/build-agents.sh
|
||||
146→- **Build Log:** /var/log/gururmm-build.log
|
||||
147→- **Gitea Webhook ID:** 1
|
||||
148→- **Trigger:** Push to main branch
|
||||
149→- **Builds:** Linux (x86_64) and Windows (x86_64) agents
|
||||
150→- **Deploy Path:** /var/www/gururmm/downloads/
|
||||
151→
|
||||
152→### Build Server SSH Key (for Gitea)
|
||||
153→- **Key Name:** gururmm-build-server
|
||||
154→- **Public Key:**
|
||||
155→```
|
||||
156→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKSqf2/phEXUK8vd5GhMIDTEGSk0LvYk92sRdNiRrjKi guru@gururmm-build
|
||||
157→```
|
||||
158→- **Added to:** Gitea (azcomputerguru account)
|
||||
159→
|
||||
160→### Clients & Sites
|
||||
161→#### Glaztech Industries (GLAZ)
|
||||
162→- **Client ID:** d857708c-5713-4ee5-a314-679f86d2f9f9
|
||||
163→- **Site:** SLC - Salt Lake City
|
||||
164→- **Site ID:** 290bd2ea-4af5-49c6-8863-c6d58c5a55de
|
||||
165→- **Site Code:** DARK-GROVE-7839
|
||||
166→- **API Key:** grmm_Qw64eawPBjnMdwN5UmDGWoPlqwvjM7lI
|
||||
167→- **Created:** 2025-12-18
|
||||
168→
|
||||
169→---
|
||||
170→
|
||||
171→## Client Sites - WHM/cPanel
|
||||
172→
|
||||
173→### IX Server (ix.azcomputerguru.com)
|
||||
174→- **SSH Host:** ix.azcomputerguru.com
|
||||
175→- **Internal IP:** 172.16.3.10 (VPN required)
|
||||
176→- **SSH User:** root
|
||||
177→- **SSH Password:** Gptf*77ttb!@#!@#
|
||||
178→- **SSH Key:** guru@wsl key added to authorized_keys
|
||||
179→- **Role:** cPanel/WHM server hosting client sites
|
||||
180→
|
||||
181→### WebSvr (websvr.acghosting.com)
|
||||
182→- **Host:** websvr.acghosting.com
|
||||
183→- **SSH User:** root
|
||||
184→- **SSH Password:** r3tr0gradE99#
|
||||
185→- **API Token:** 8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O
|
||||
186→- **Access Level:** Full access
|
||||
187→- **Role:** Legacy cPanel/WHM server (migration source to IX)
|
||||
188→
|
||||
189→### data.grabbanddurando.com
|
||||
190→- **Server:** IX (ix.azcomputerguru.com)
|
||||
191→- **cPanel Account:** grabblaw
|
||||
192→- **Site Path:** /home/grabblaw/public_html/data_grabbanddurando
|
||||
193→- **Site Admin User:** admin
|
||||
194→- **Site Admin Password:** GND-Paper123!@#-datasite
|
||||
195→- **Database:** grabblaw_gdapp_data
|
||||
196→- **DB User:** grabblaw_gddata
|
||||
197→- **DB Password:** GrabbData2025
|
||||
198→- **Config File:** /home/grabblaw/public_html/data_grabbanddurando/connection.php
|
||||
199→- **Backups:** /home/grabblaw/public_html/data_grabbanddurando/backups_mariadb_fix/
|
||||
200→
|
||||
201→### GoDaddy VPS (Legacy)
|
||||
202→- **IP:** 208.109.235.224
|
||||
203→- **Hostname:** 224.235.109.208.host.secureserver.net
|
||||
204→- **Auth:** SSH key
|
||||
205→- **Database:** grabblaw_gdapp
|
||||
206→- **Note:** Old server, data migrated to IX
|
||||
207→
|
||||
208→---
|
||||
209→
|
||||
210→## Seafile (on Jupiter - Migrated 2025-12-27)
|
||||
211→
|
||||
212→### Container
|
||||
213→- **Host:** Jupiter (172.16.3.20)
|
||||
214→- **URL:** https://sync.azcomputerguru.com
|
||||
215→- **Port:** 8082 (internal), proxied via NPM
|
||||
216→- **Containers:** seafile, seafile-mysql, seafile-memcached, seafile-elasticsearch
|
||||
217→- **Docker Compose:** /mnt/user0/SeaFile/DockerCompose/docker-compose.yml
|
||||
218→- **Data Path:** /mnt/user0/SeaFile/seafile-data/
|
||||
219→
|
||||
220→### Seafile Admin
|
||||
221→- **Email:** mike@azcomputerguru.com
|
||||
222→- **Password:** r3tr0gradE99#
|
||||
223→
|
||||
224→### Database (MariaDB)
|
||||
225→- **Container:** seafile-mysql
|
||||
226→- **Image:** mariadb:10.6
|
||||
227→- **Root Password:** db_dev
|
||||
228→- **Seafile User:** seafile
|
||||
229→- **Seafile Password:** 64f2db5e-6831-48ed-a243-d4066fe428f9
|
||||
230→- **Databases:** ccnet_db (users), seafile_db (data), seahub_db (web)
|
||||
231→
|
||||
232→### Elasticsearch
|
||||
233→- **Container:** seafile-elasticsearch
|
||||
234→- **Image:** elasticsearch:7.17.26
|
||||
235→- **Note:** Upgraded from 7.16.2 for kernel 6.12 compatibility
|
||||
236→
|
||||
237→### Microsoft Graph API (Email)
|
||||
238→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
239→- **Client ID:** 15b0fafb-ab51-4cc9-adc7-f6334c805c22
|
||||
240→- **Client Secret:** rRN8Q~FPfSL8O24iZthi_LVJTjGOCZG.DnxGHaSk
|
||||
241→- **Sender Email:** noreply@azcomputerguru.com
|
||||
242→- **Used for:** Seafile email notifications via Graph API
|
||||
243→
|
||||
244→### Migration Notes
|
||||
245→- **Migrated from:** Saturn (172.16.3.21) on 2025-12-27
|
||||
246→- **Saturn Status:** Seafile stopped, data intact for rollback (keep 1 week)
|
||||
247→
|
||||
248→---
|
||||
249→
|
||||
250→## NPM Proxy Hosts Reference
|
||||
251→
|
||||
252→| ID | Domain | Backend | SSL Cert |
|
||||
253→|----|--------|---------|----------|
|
||||
254→| 1 | emby.azcomputerguru.com | 172.16.2.99:8096 | npm-1 |
|
||||
255→| 2 | git.azcomputerguru.com | 172.16.3.20:3000 | npm-2 |
|
||||
256→| 4 | plexrequest.azcomputerguru.com | 172.16.3.31:5055 | npm-4 |
|
||||
257→| 5 | rmm-api.azcomputerguru.com | 172.16.3.20:3001 | npm-6 |
|
||||
258→| - | unifi.azcomputerguru.com | 172.16.3.28:8443 | npm-5 |
|
||||
259→| 8 | sync.azcomputerguru.com | 172.16.3.20:8082 | npm-8 |
|
||||
260→
|
||||
261→---
|
||||
262→
|
||||
263→## Tailscale Network
|
||||
264→
|
||||
265→| Tailscale IP | Hostname | Owner | OS |
|
||||
266→|--------------|----------|-------|-----|
|
||||
267→| 100.79.69.82 (pfsense-1) | pfsense | mike@ | freebsd |
|
||||
268→| 100.125.36.6 | acg-m-l5090 | mike@ | windows |
|
||||
269→| 100.92.230.111 | acg-tech-01l | mike@ | windows |
|
||||
270→| 100.96.135.117 | acg-tech-02l | mike@ | windows |
|
||||
271→| 100.113.45.7 | acg-tech03l | howard@ | windows |
|
||||
272→| 100.77.166.22 | desktop-hjfjtep | mike@ | windows |
|
||||
273→| 100.101.145.100 | guru-legion9 | mike@ | windows |
|
||||
274→| 100.119.194.51 | guru-surface8 | howard@ | windows |
|
||||
275→| 100.66.103.110 | magus-desktop | rob@ | windows |
|
||||
276→| 100.66.167.120 | magus-pc | rob@ | windows |
|
||||
277→
|
||||
278→---
|
||||
279→
|
||||
280→## SSH Public Keys
|
||||
281→
|
||||
282→### guru@wsl (Windows/WSL)
|
||||
283→- **User:** guru
|
||||
284→- **Sudo Password:** Window123!@#-wsl
|
||||
285→- **SSH Key:**
|
||||
286→```
|
||||
287→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWY+SdqMHJP5JOe3qpWENQZhXJA4tzI2d7ZVNAwA/1u guru@wsl
|
||||
288→```
|
||||
289→
|
||||
290→### azcomputerguru@local (Mac)
|
||||
291→- **User:** azcomputerguru
|
||||
292→- **SSH Key:**
|
||||
293→```
|
||||
294→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDrGbr4EwvQ4P3ZtyZW3ZKkuDQOMbqyAQUul2+JE4K4S azcomputerguru@local
|
||||
295→```
|
||||
296→
|
||||
297→---
|
||||
298→
|
||||
299→## Quick Reference Commands
|
||||
300→
|
||||
301→### NPM API Auth
|
||||
302→```bash
|
||||
303→curl -s -X POST http://172.16.3.20:7818/api/tokens \
|
||||
304→ -H "Content-Type: application/json" \
|
||||
305→ -d '{"identity":"mike@azcomputerguru.com","secret":"Paper123!@#-unifi"}'
|
||||
306→```
|
||||
307→
|
||||
308→### Gitea API
|
||||
309→```bash
|
||||
310→curl -H "Authorization: token 9b1da4b79a38ef782268341d25a4b6880572063f" \
|
||||
311→ https://git.azcomputerguru.com/api/v1/repos/search
|
||||
312→```
|
||||
313→
|
||||
314→### GuruRMM Health Check
|
||||
315→```bash
|
||||
316→curl http://172.16.3.20:3001/health
|
||||
317→```
|
||||
318→
|
||||
319→---
|
||||
320→
|
||||
321→## MSP Tools
|
||||
322→
|
||||
323→### Syncro (PSA/RMM) - AZ Computer Guru
|
||||
324→- **API Key:** T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3
|
||||
325→- **Subdomain:** computerguru
|
||||
326→- **API Base URL:** https://computerguru.syncromsp.com/api/v1
|
||||
327→- **API Docs:** https://api-docs.syncromsp.com/
|
||||
328→- **Account:** AZ Computer Guru MSP
|
||||
329→- **Notes:** Added 2025-12-18
|
||||
330→
|
||||
331→### Autotask (PSA) - AZ Computer Guru
|
||||
332→- **API Username:** dguyqap2nucge6r@azcomputerguru.com
|
||||
333→- **API Password:** z*6G4fT#oM~8@9Hxy$2Y7K$ma
|
||||
334→- **API Integration Code:** HYTYYZ6LA5HB5XK7IGNA7OAHQLH
|
||||
335→- **Integration Name:** ClaudeAPI
|
||||
336→- **API Zone:** webservices5.autotask.net
|
||||
337→- **API Docs:** https://autotask.net/help/developerhelp/Content/APIs/REST/REST_API_Home.htm
|
||||
338→- **Account:** AZ Computer Guru MSP
|
||||
339→- **Notes:** Added 2025-12-18, new API user "Claude API"
|
||||
340→
|
||||
341→### CIPP (CyberDrain Improved Partner Portal)
|
||||
342→- **URL:** https://cippcanvb.azurewebsites.net
|
||||
343→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
344→- **API Client Name:** ClaudeCipp2 (working)
|
||||
345→- **App ID (Client ID):** 420cb849-542d-4374-9cb2-3d8ae0e1835b
|
||||
346→- **Client Secret:** MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT
|
||||
347→- **Scope:** api://420cb849-542d-4374-9cb2-3d8ae0e1835b/.default
|
||||
348→- **CIPP-SAM App ID:** 91b9102d-bafd-43f8-b17a-f99479149b07
|
||||
349→- **IP Range:** 0.0.0.0/0 (all IPs allowed)
|
||||
350→- **Auth Method:** OAuth 2.0 Client Credentials
|
||||
351→- **Notes:** Updated 2025-12-23, working API client
|
||||
352→
|
||||
353→#### CIPP API Usage (Bash)
|
||||
354→```bash
|
||||
355→# Get token
|
||||
356→ACCESS_TOKEN=$(curl -s -X POST "https://login.microsoftonline.com/ce61461e-81a0-4c84-bb4a-7b354a9a356d/oauth2/v2.0/token" \
|
||||
357→ -d "client_id=420cb849-542d-4374-9cb2-3d8ae0e1835b" \
|
||||
358→ -d "client_secret=MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT" \
|
||||
359→ -d "scope=api://420cb849-542d-4374-9cb2-3d8ae0e1835b/.default" \
|
||||
360→ -d "grant_type=client_credentials" | python3 -c "import sys, json; print(json.load(sys.stdin).get('access_token', ''))")
|
||||
361→
|
||||
362→# Query endpoints (use tenant domain or tenant ID as TenantFilter)
|
||||
363→curl -s "https://cippcanvb.azurewebsites.net/api/ListLicenses?TenantFilter=sonorangreenllc.com" \
|
||||
364→ -H "Authorization: Bearer ${ACCESS_TOKEN}"
|
||||
365→
|
||||
366→# Other useful endpoints:
|
||||
367→# ListTenants?AllTenants=true - List all managed tenants
|
||||
368→# ListUsers?TenantFilter={tenant} - List users
|
||||
369→# ListMailboxRules?TenantFilter={tenant} - Check mailbox rules
|
||||
370→# BECCheck?TenantFilter={tenant}&UserID={userid} - BEC investigation
|
||||
371→```
|
||||
372→
|
||||
373→#### Old API Client (403 errors - do not use)
|
||||
374→- **App ID:** d545a836-7118-44f6-8852-d9dd64fb7bb9
|
||||
375→- **Status:** Authenticated but all endpoints returned 403
|
||||
376→
|
||||
377→### Claude-MSP-Access (Multi-Tenant Graph API)
|
||||
378→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
379→- **App ID (Client ID):** fabb3421-8b34-484b-bc17-e46de9703418
|
||||
380→- **Client Secret:** ~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO
|
||||
381→- **Secret Expires:** 2026-12 (24 months)
|
||||
382→- **Sign-in Audience:** Multi-tenant (any Entra ID org)
|
||||
383→- **Purpose:** Direct Graph API access for M365 investigations and remediation
|
||||
384→- **Admin Consent URL:** https://login.microsoftonline.com/common/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
|
||||
385→- **Permissions:** User.ReadWrite.All, Directory.ReadWrite.All, Mail.ReadWrite, MailboxSettings.ReadWrite, AuditLog.Read.All, Application.ReadWrite.All, DelegatedPermissionGrant.ReadWrite.All, Group.ReadWrite.All, SecurityEvents.ReadWrite.All, AppRoleAssignment.ReadWrite.All, UserAuthenticationMethod.ReadWrite.All
|
||||
386→- **Created:** 2025-12-29
|
||||
387→
|
||||
388→#### Usage (Python)
|
||||
389→```python
|
||||
390→import requests
|
||||
391→
|
||||
392→tenant_id = "CUSTOMER_TENANT_ID" # or use 'common' after consent
|
||||
393→client_id = "fabb3421-8b34-484b-bc17-e46de9703418"
|
||||
394→client_secret = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
||||
395→
|
||||
396→# Get token
|
||||
397→token_resp = requests.post(
|
||||
398→ f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
|
||||
399→ data={
|
||||
400→ "client_id": client_id,
|
||||
401→ "client_secret": client_secret,
|
||||
402→ "scope": "https://graph.microsoft.com/.default",
|
||||
403→ "grant_type": "client_credentials"
|
||||
404→ }
|
||||
405→)
|
||||
406→access_token = token_resp.json()["access_token"]
|
||||
407→
|
||||
408→# Query Graph API
|
||||
409→headers = {"Authorization": f"Bearer {access_token}"}
|
||||
410→users = requests.get("https://graph.microsoft.com/v1.0/users", headers=headers)
|
||||
411→```
|
||||
412→
|
||||
413→---
|
||||
414→
|
||||
415→## Client - MVAN Inc
|
||||
416→
|
||||
417→### Microsoft 365 Tenant 1
|
||||
418→- **Tenant:** mvan.onmicrosoft.com
|
||||
419→- **Admin User:** sysadmin@mvaninc.com
|
||||
420→- **Password:** r3tr0gradE99#
|
||||
421→- **Notes:** Global admin, project to merge/trust with T2
|
||||
422→
|
||||
423→---
|
||||
424→
|
||||
425→## Client - BG Builders LLC
|
||||
426→
|
||||
427→### Microsoft 365 Tenant
|
||||
428→- **Tenant:** bgbuildersllc.com
|
||||
429→- **CIPP Name:** sonorangreenllc.com
|
||||
430→- **Tenant ID:** ededa4fb-f6eb-4398-851d-5eb3e11fab27
|
||||
431→- **Admin User:** sysadmin@bgbuildersllc.com
|
||||
432→- **Password:** Window123!@#-bgb
|
||||
433→- **Notes:** Added 2025-12-19
|
||||
434→
|
||||
435→### Security Investigation (2025-12-22)
|
||||
436→- **Compromised User:** Shelly@bgbuildersllc.com (Shelly Dooley)
|
||||
437→- **Symptoms:** Suspicious sent items reported by user
|
||||
438→- **Findings:**
|
||||
439→ - Gmail OAuth app with EAS.AccessAsUser.All (REMOVED)
|
||||
440→ - "P2P Server" app registration backdoor (DELETED by admin)
|
||||
441→ - No malicious mailbox rules or forwarding
|
||||
442→ - Sign-in logs unavailable (no Entra P1 license)
|
||||
443→- **Remediation:**
|
||||
444→ - Password reset: `5ecwyHv6&dP7` (must change on login)
|
||||
445→ - All sessions revoked
|
||||
446→ - Gmail OAuth consent removed
|
||||
447→ - P2P Server backdoor deleted
|
||||
448→- **Status:** RESOLVED
|
||||
449→
|
||||
450→---
|
||||
451→
|
||||
452→## Client - Dataforth
|
||||
453→
|
||||
454→### Network
|
||||
455→- **Subnet:** 192.168.0.0/24
|
||||
456→- **Domain:** INTRANET (intranet.dataforth.com)
|
||||
457→
|
||||
458→### UDM (Unifi Dream Machine)
|
||||
459→- **IP:** 192.168.0.254
|
||||
460→- **SSH User:** root
|
||||
461→- **SSH Password:** Paper123!@#-unifi
|
||||
462→- **Web User:** azcomputerguru
|
||||
463→- **Web Password:** Paper123!@#-unifi
|
||||
464→- **2FA:** Push notification enabled
|
||||
465→- **Notes:** Gateway/firewall, OpenVPN server
|
||||
466→
|
||||
467→### AD1 (Domain Controller)
|
||||
468→- **IP:** 192.168.0.27
|
||||
469→- **Hostname:** AD1.intranet.dataforth.com
|
||||
470→- **User:** INTRANET\sysadmin
|
||||
471→- **Password:** Paper123!@#
|
||||
472→- **Role:** Primary DC, NPS/RADIUS server
|
||||
473→- **NPS Ports:** 1812/1813 (auth/accounting)
|
||||
474→
|
||||
475→### AD2 (Domain Controller)
|
||||
476→- **IP:** 192.168.0.6
|
||||
477→- **Hostname:** AD2.intranet.dataforth.com
|
||||
478→- **User:** INTRANET\sysadmin
|
||||
479→- **Password:** Paper123!@#
|
||||
480→- **Role:** Secondary DC, file server
|
||||
481→
|
||||
482→### NPS RADIUS Configuration
|
||||
483→- **Client Name:** unifi
|
||||
484→- **Client IP:** 192.168.0.254
|
||||
485→- **Shared Secret:** Gptf*77ttb!@#!@#
|
||||
486→- **Policy:** "Unifi" - allows Domain Users
|
||||
487→
|
||||
488→### D2TESTNAS (SMB1 Proxy)
|
||||
489→- **IP:** 192.168.0.9
|
||||
490→- **Web/SSH User:** admin
|
||||
491→- **Web/SSH Password:** Paper123!@#-nas
|
||||
492→- **Role:** DOS machine SMB1 proxy
|
||||
493→- **Notes:** Added 2025-12-14
|
||||
494→
|
||||
495→---
|
||||
496→
|
||||
497→## Client - Valley Wide Plastering
|
||||
498→
|
||||
499→### Network
|
||||
500→- **Subnet:** 172.16.9.0/24
|
||||
501→
|
||||
502→### UDM (UniFi Dream Machine)
|
||||
503→- **IP:** 172.16.9.1
|
||||
504→- **SSH User:** root
|
||||
505→- **SSH Password:** Gptf*77ttb123!@#-vwp
|
||||
506→- **Notes:** Gateway/firewall, VPN server, RADIUS client
|
||||
507→
|
||||
508→### VWP-DC1 (Domain Controller)
|
||||
509→- **IP:** 172.16.9.2
|
||||
510→- **Hostname:** VWP-DC1
|
||||
511→- **User:** sysadmin
|
||||
512→- **Password:** r3tr0gradE99#
|
||||
513→- **Role:** Primary DC, NPS/RADIUS server
|
||||
514→- **Notes:** Added 2025-12-22
|
||||
515→
|
||||
516→### NPS RADIUS Configuration
|
||||
517→- **RADIUS Server:** 172.16.9.2
|
||||
518→- **RADIUS Ports:** 1812 (auth), 1813 (accounting)
|
||||
519→- **Clients:** UDM (172.16.9.1), VWP-Subnet (172.16.9.0/24)
|
||||
520→- **Shared Secret:** Gptf*77ttb123!@#-radius
|
||||
521→- **Policy:** "VPN-Access" - allows all authenticated users (24/7)
|
||||
522→- **Auth Methods:** All (PAP, CHAP, MS-CHAP, MS-CHAPv2, EAP)
|
||||
523→- **User Dial-in:** All VWP_Users set to Allow
|
||||
524→- **AuthAttributeRequired:** Disabled on clients
|
||||
525→- **Tested:** 2025-12-22, user cguerrero authenticated successfully
|
||||
526→
|
||||
527→### Dataforth - Entra App Registration (Claude-Code-M365)
|
||||
528→- **Tenant ID:** 7dfa3ce8-c496-4b51-ab8d-bd3dcd78b584
|
||||
529→- **App ID (Client ID):** 7a8c0b2e-57fb-4d79-9b5a-4b88d21b1f29
|
||||
530→- **Client Secret:** tXo8Q~ZNG9zoBpbK9HwJTkzx.YEigZ9AynoSrca3
|
||||
531→- **Permissions:** Calendars.ReadWrite, Contacts.ReadWrite, User.ReadWrite.All, Mail.ReadWrite, Directory.ReadWrite.All, Group.ReadWrite.All
|
||||
532→- **Created:** 2025-12-22
|
||||
533→- **Use:** Silent Graph API access to Dataforth tenant
|
||||
534→
|
||||
535→---
|
||||
536→
|
||||
537→## Client - CW Concrete LLC
|
||||
538→
|
||||
539→### Microsoft 365 Tenant
|
||||
540→- **Tenant:** cwconcretellc.com
|
||||
541→- **CIPP Name:** cwconcretellc.com
|
||||
542→- **Tenant ID:** dfee2224-93cd-4291-9b09-6c6ce9bb8711
|
||||
543→- **Default Domain:** NETORGFT11452752.onmicrosoft.com
|
||||
544→- **Notes:** De-federated from GoDaddy 2025-12, domain needs re-verification
|
||||
545→
|
||||
546→### Security Investigation (2025-12-22)
|
||||
547→- **Findings:**
|
||||
548→ - Graph Command Line Tools OAuth consent with high privileges (REMOVED)
|
||||
549→ - "test" backdoor app registration with multi-tenant access (DELETED)
|
||||
550→ - Apple Internet Accounts OAuth (left - likely iOS device)
|
||||
551→ - No malicious mailbox rules or forwarding
|
||||
552→- **Remediation:**
|
||||
553→ - All sessions revoked for all 4 users
|
||||
554→ - Backdoor apps removed
|
||||
555→- **Status:** RESOLVED
|
||||
556→
|
||||
557→---
|
||||
558→
|
||||
559→## Client - Khalsa
|
||||
560→
|
||||
561→### Network
|
||||
562→- **Subnet:** 172.16.50.0/24
|
||||
563→
|
||||
564→### UCG (UniFi Cloud Gateway)
|
||||
565→- **IP:** 172.16.50.1
|
||||
566→- **SSH User:** azcomputerguru
|
||||
567→- **SSH Password:** Paper123!@#-camden (reset 2025-12-22)
|
||||
568→- **Notes:** Gateway/firewall, VPN server, SSH key added but not working
|
||||
569→
|
||||
570→### Switch
|
||||
571→- **User:** 8WfY8
|
||||
572→- **Password:** tI3evTNBZMlnngtBc
|
||||
573→
|
||||
574→### Accountant Machine
|
||||
575→- **IP:** 172.16.50.168
|
||||
576→- **User:** accountant
|
||||
577→- **Password:** Paper123!@#-accountant
|
||||
578→- **Notes:** Added 2025-12-22, VPN routing issue
|
||||
579→
|
||||
580→---
|
||||
581→
|
||||
582→## Client - Scileppi Law Firm
|
||||
583→
|
||||
584→### DS214se (Source NAS - being migrated)
|
||||
585→- **IP:** 172.16.1.54
|
||||
586→- **SSH User:** admin
|
||||
587→- **Password:** Th1nk3r^99
|
||||
588→- **Storage:** 1.8TB (1.6TB used)
|
||||
589→- **Data:** User home folders (admin, Andrew Ross, Chris Scileppi, Samantha Nunez, etc.)
|
||||
590→
|
||||
591→### Unraid (Source - Migration)
|
||||
592→- **IP:** 172.16.1.21
|
||||
593→- **SSH User:** root
|
||||
594→- **Password:** Th1nk3r^99
|
||||
595→- **Role:** Data source for migration to RS2212+
|
||||
596→
|
||||
597→### RS2212+ (Destination NAS)
|
||||
598→- **IP:** 172.16.1.59
|
||||
599→- **Hostname:** SL-SERVER
|
||||
600→- **SSH User:** sysadmin
|
||||
601→- **Password:** Gptf*77ttb123!@#-sl-server
|
||||
602→- **SSH Key:** claude-code@localadmin added to authorized_keys
|
||||
603→- **Storage:** 25TB total, 6.9TB used (28%)
|
||||
604→- **Data Share:** /volume1/Data (7.9TB - Active, Closed, Archived, Billing, MOTIONS BANK)
|
||||
605→- **Notes:** Migration and consolidation complete 2025-12-29
|
||||
606→
|
||||
607→### RS2212+ User Accounts (Created 2025-12-29)
|
||||
608→| Username | Full Name | Password | Notes |
|
||||
609→|----------|-----------|----------|-------|
|
||||
610→| chris | Chris Scileppi | Scileppi2025! | Owner |
|
||||
611→| andrew | Andrew Ross | Scileppi2025! | Staff |
|
||||
612→| sylvia | Sylvia | Scileppi2025! | Staff |
|
||||
613→| rose | Rose | Scileppi2025! | Staff |
|
||||
614→| (TBD) | 5th user | - | Name pending |
|
||||
615→
|
||||
616→### Migration/Consolidation Status (COMPLETE)
|
||||
617→- **Completed:** 2025-12-29
|
||||
618→- **Final Structure:**
|
||||
619→ - Active: 2.5TB (merged Unraid + DS214se Open Cases)
|
||||
620→ - Closed: 4.9TB (merged Unraid + DS214se Closed Cases)
|
||||
621→ - Archived: 451GB
|
||||
622→ - MOTIONS BANK: 21MB
|
||||
623→ - Billing: 17MB
|
||||
624→- **Recycle Bin:** Emptied (recovered 413GB)
|
||||
625→- **Permissions:** Group "users" with 775 on /volume1/Data
|
||||
626→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,4 @@
|
||||
remote: . Processing 1 references
|
||||
remote: Processed 1 references in total
|
||||
To https://git.azcomputerguru.com/azcomputerguru/guru-connect.git
|
||||
3c2e070..1cc94c6 main -> main
|
||||
@@ -0,0 +1,2 @@
|
||||
[main 1cc94c6] Add is_online/is_persistent for persistent agent sessions
|
||||
4 files changed, 81 insertions(+), 8 deletions(-)
|
||||
@@ -0,0 +1,7 @@
|
||||
1→[2m2025-12-30T04:04:03.724128Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m INITIAL ADMIN USER CREATED
|
||||
2→[2m2025-12-30T04:04:03.724145Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m Password: uwYmX6aygmJ@ZGqv
|
||||
3→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
Binary file not shown.
@@ -0,0 +1,2 @@
|
||||
Exit code 127
|
||||
bash: line 1: cargo: command not found
|
||||
@@ -0,0 +1,216 @@
|
||||
1→//! REST API endpoints
|
||||
2→
|
||||
3→pub mod auth;
|
||||
4→pub mod users;
|
||||
5→
|
||||
6→use axum::{
|
||||
7→ extract::{Path, State, Query},
|
||||
8→ Json,
|
||||
9→};
|
||||
10→use serde::{Deserialize, Serialize};
|
||||
11→use uuid::Uuid;
|
||||
12→
|
||||
13→use crate::session::SessionManager;
|
||||
14→use crate::db;
|
||||
15→
|
||||
16→/// Viewer info returned by API
|
||||
17→#[derive(Debug, Serialize)]
|
||||
18→pub struct ViewerInfoApi {
|
||||
19→ pub id: String,
|
||||
20→ pub name: String,
|
||||
21→ pub connected_at: String,
|
||||
22→}
|
||||
23→
|
||||
24→impl From<crate::session::ViewerInfo> for ViewerInfoApi {
|
||||
25→ fn from(v: crate::session::ViewerInfo) -> Self {
|
||||
26→ Self {
|
||||
27→ id: v.id,
|
||||
28→ name: v.name,
|
||||
29→ connected_at: v.connected_at.to_rfc3339(),
|
||||
30→ }
|
||||
31→ }
|
||||
32→}
|
||||
33→
|
||||
34→/// Session info returned by API
|
||||
35→#[derive(Debug, Serialize)]
|
||||
36→pub struct SessionInfo {
|
||||
37→ pub id: String,
|
||||
38→ pub agent_id: String,
|
||||
39→ pub agent_name: String,
|
||||
40→ pub started_at: String,
|
||||
41→ pub viewer_count: usize,
|
||||
42→ pub viewers: Vec<ViewerInfoApi>,
|
||||
43→ pub is_streaming: bool,
|
||||
44→ pub is_online: bool,
|
||||
45→ pub is_persistent: bool,
|
||||
46→ pub last_heartbeat: String,
|
||||
47→ pub os_version: Option<String>,
|
||||
48→ pub is_elevated: bool,
|
||||
49→ pub uptime_secs: i64,
|
||||
50→ pub display_count: i32,
|
||||
51→}
|
||||
52→
|
||||
53→impl From<crate::session::Session> for SessionInfo {
|
||||
54→ fn from(s: crate::session::Session) -> Self {
|
||||
55→ Self {
|
||||
56→ id: s.id.to_string(),
|
||||
57→ agent_id: s.agent_id,
|
||||
58→ agent_name: s.agent_name,
|
||||
59→ started_at: s.started_at.to_rfc3339(),
|
||||
60→ viewer_count: s.viewer_count,
|
||||
61→ viewers: s.viewers.into_iter().map(ViewerInfoApi::from).collect(),
|
||||
62→ is_streaming: s.is_streaming,
|
||||
63→ is_online: s.is_online,
|
||||
64→ is_persistent: s.is_persistent,
|
||||
65→ last_heartbeat: s.last_heartbeat.to_rfc3339(),
|
||||
66→ os_version: s.os_version,
|
||||
67→ is_elevated: s.is_elevated,
|
||||
68→ uptime_secs: s.uptime_secs,
|
||||
69→ display_count: s.display_count,
|
||||
70→ }
|
||||
71→ }
|
||||
72→}
|
||||
73→
|
||||
74→/// List all active sessions
|
||||
75→pub async fn list_sessions(
|
||||
76→ State(sessions): State<SessionManager>,
|
||||
77→) -> Json<Vec<SessionInfo>> {
|
||||
78→ let sessions = sessions.list_sessions().await;
|
||||
79→ Json(sessions.into_iter().map(SessionInfo::from).collect())
|
||||
80→}
|
||||
81→
|
||||
82→/// Get a specific session by ID
|
||||
83→pub async fn get_session(
|
||||
84→ State(sessions): State<SessionManager>,
|
||||
85→ Path(id): Path<String>,
|
||||
86→) -> Result<Json<SessionInfo>, (axum::http::StatusCode, &'static str)> {
|
||||
87→ let session_id = Uuid::parse_str(&id)
|
||||
88→ .map_err(|_| (axum::http::StatusCode::BAD_REQUEST, "Invalid session ID"))?;
|
||||
89→
|
||||
90→ let session = sessions.get_session(session_id).await
|
||||
91→ .ok_or((axum::http::StatusCode::NOT_FOUND, "Session not found"))?;
|
||||
92→
|
||||
93→ Ok(Json(SessionInfo::from(session)))
|
||||
94→}
|
||||
95→
|
||||
96→// ============================================================================
|
||||
97→// Machine API Types
|
||||
98→// ============================================================================
|
||||
99→
|
||||
100→/// Machine info returned by API
|
||||
101→#[derive(Debug, Serialize)]
|
||||
102→pub struct MachineInfo {
|
||||
103→ pub id: String,
|
||||
104→ pub agent_id: String,
|
||||
105→ pub hostname: String,
|
||||
106→ pub os_version: Option<String>,
|
||||
107→ pub is_elevated: bool,
|
||||
108→ pub is_persistent: bool,
|
||||
109→ pub first_seen: String,
|
||||
110→ pub last_seen: String,
|
||||
111→ pub status: String,
|
||||
112→}
|
||||
113→
|
||||
114→impl From<db::machines::Machine> for MachineInfo {
|
||||
115→ fn from(m: db::machines::Machine) -> Self {
|
||||
116→ Self {
|
||||
117→ id: m.id.to_string(),
|
||||
118→ agent_id: m.agent_id,
|
||||
119→ hostname: m.hostname,
|
||||
120→ os_version: m.os_version,
|
||||
121→ is_elevated: m.is_elevated,
|
||||
122→ is_persistent: m.is_persistent,
|
||||
123→ first_seen: m.first_seen.to_rfc3339(),
|
||||
124→ last_seen: m.last_seen.to_rfc3339(),
|
||||
125→ status: m.status,
|
||||
126→ }
|
||||
127→ }
|
||||
128→}
|
||||
129→
|
||||
130→/// Session record for history
|
||||
131→#[derive(Debug, Serialize)]
|
||||
132→pub struct SessionRecord {
|
||||
133→ pub id: String,
|
||||
134→ pub started_at: String,
|
||||
135→ pub ended_at: Option<String>,
|
||||
136→ pub duration_secs: Option<i32>,
|
||||
137→ pub is_support_session: bool,
|
||||
138→ pub support_code: Option<String>,
|
||||
139→ pub status: String,
|
||||
140→}
|
||||
141→
|
||||
142→impl From<db::sessions::DbSession> for SessionRecord {
|
||||
143→ fn from(s: db::sessions::DbSession) -> Self {
|
||||
144→ Self {
|
||||
145→ id: s.id.to_string(),
|
||||
146→ started_at: s.started_at.to_rfc3339(),
|
||||
147→ ended_at: s.ended_at.map(|t| t.to_rfc3339()),
|
||||
148→ duration_secs: s.duration_secs,
|
||||
149→ is_support_session: s.is_support_session,
|
||||
150→ support_code: s.support_code,
|
||||
151→ status: s.status,
|
||||
152→ }
|
||||
153→ }
|
||||
154→}
|
||||
155→
|
||||
156→/// Event record for history
|
||||
157→#[derive(Debug, Serialize)]
|
||||
158→pub struct EventRecord {
|
||||
159→ pub id: i64,
|
||||
160→ pub session_id: String,
|
||||
161→ pub event_type: String,
|
||||
162→ pub timestamp: String,
|
||||
163→ pub viewer_id: Option<String>,
|
||||
164→ pub viewer_name: Option<String>,
|
||||
165→ pub details: Option<serde_json::Value>,
|
||||
166→ pub ip_address: Option<String>,
|
||||
167→}
|
||||
168→
|
||||
169→impl From<db::events::SessionEvent> for EventRecord {
|
||||
170→ fn from(e: db::events::SessionEvent) -> Self {
|
||||
171→ Self {
|
||||
172→ id: e.id,
|
||||
173→ session_id: e.session_id.to_string(),
|
||||
174→ event_type: e.event_type,
|
||||
175→ timestamp: e.timestamp.to_rfc3339(),
|
||||
176→ viewer_id: e.viewer_id,
|
||||
177→ viewer_name: e.viewer_name,
|
||||
178→ details: e.details,
|
||||
179→ ip_address: e.ip_address,
|
||||
180→ }
|
||||
181→ }
|
||||
182→}
|
||||
183→
|
||||
184→/// Full machine history (for export)
|
||||
185→#[derive(Debug, Serialize)]
|
||||
186→pub struct MachineHistory {
|
||||
187→ pub machine: MachineInfo,
|
||||
188→ pub sessions: Vec<SessionRecord>,
|
||||
189→ pub events: Vec<EventRecord>,
|
||||
190→ pub exported_at: String,
|
||||
191→}
|
||||
192→
|
||||
193→/// Query parameters for machine deletion
|
||||
194→#[derive(Debug, Deserialize)]
|
||||
195→pub struct DeleteMachineParams {
|
||||
196→ /// If true, send uninstall command to agent (if online)
|
||||
197→ #[serde(default)]
|
||||
198→ pub uninstall: bool,
|
||||
199→ /// If true, include history in response before deletion
|
||||
200→ #[serde(default)]
|
||||
201→ pub export: bool,
|
||||
202→}
|
||||
203→
|
||||
204→/// Response for machine deletion
|
||||
205→#[derive(Debug, Serialize)]
|
||||
206→pub struct DeleteMachineResponse {
|
||||
207→ pub success: bool,
|
||||
208→ pub message: String,
|
||||
209→ pub uninstall_sent: bool,
|
||||
210→ pub history: Option<MachineHistory>,
|
||||
211→}
|
||||
212→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1 @@
|
||||
C:\Users\MikeSwanson\Claude\session-logs\2025-12-29-session.md
|
||||
@@ -0,0 +1,17 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\main.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
15→ include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
|
||||
16→}
|
||||
17→
|
||||
18→use anyhow::Result;
|
||||
19→use axum::{
|
||||
20→ Router,
|
||||
21→ routing::{get, post, put, delete},
|
||||
22→ extract::{Path, State, Json, Query, Request},
|
||||
23→ response::{Html, IntoResponse},
|
||||
24→ http::StatusCode,
|
||||
25→ middleware::{self, Next},
|
||||
26→};
|
||||
27→use std::net::SocketAddr;
|
||||
28→use std::sync::Arc;
|
||||
29→use tower_http::cors::{Any, CorsLayer};
|
||||
30→use tower_http::trace::TraceLayer;
|
||||
@@ -0,0 +1,10 @@
|
||||
[detached HEAD 3fc4e1f] Add user management system with JWT authentication
|
||||
13 files changed, 2355 insertions(+), 71 deletions(-)
|
||||
create mode 100644 server/migrations/002_user_management.sql
|
||||
create mode 100644 server/src/api/auth.rs
|
||||
create mode 100644 server/src/api/users.rs
|
||||
create mode 100644 server/src/auth/jwt.rs
|
||||
create mode 100644 server/src/auth/password.rs
|
||||
create mode 100644 server/src/db/users.rs
|
||||
create mode 100644 server/static/users.html
|
||||
Successfully rebased and updated refs/heads/main.
|
||||
@@ -0,0 +1 @@
|
||||
Cloning into 'guru-connect'...
|
||||
@@ -0,0 +1 @@
|
||||
Command running in background with ID: b70d20e. Output is being written to: C:\Users\MIKESW~1\AppData\Local\Temp\claude\C--Users-MikeSwanson-Claude\tasks\b70d20e.output
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 127
|
||||
bash: line 1: cargo: command not found
|
||||
@@ -0,0 +1 @@
|
||||
Cloning into 'guru-connect'...
|
||||
@@ -0,0 +1,32 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\static\dashboard.html has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
697→ container.innerHTML = '<div class="empty-state"><h3>Select a machine</h3><p>Click a machine to view details</p></div>';
|
||||
698→ return;
|
||||
699→ }
|
||||
700→
|
||||
701→ const m = selectedMachine;
|
||||
702→ const started = new Date(m.started_at).toLocaleString();
|
||||
703→ const statusColor = m.is_online ? 'hsl(142, 76%, 50%)' : 'hsl(0, 0%, 50%)';
|
||||
704→ const statusText = m.is_online ? 'Online' : 'Offline';
|
||||
705→ const connectDisabled = m.is_online ? '' : 'disabled';
|
||||
706→ const connectTitle = m.is_online ? '' : 'title="Agent is offline"';
|
||||
707→
|
||||
708→ container.innerHTML =
|
||||
709→ '<div class="detail-section">' +
|
||||
710→ '<div class="detail-section-title">Machine Info</div>' +
|
||||
711→ '<div class="detail-row"><span class="detail-label">Status</span><span class="detail-value" style="color: ' + statusColor + ';">' + statusText + '</span></div>' +
|
||||
712→ '<div class="detail-row"><span class="detail-label">Agent ID</span><span class="detail-value">' + m.agent_id.slice(0,8) + '...</span></div>' +
|
||||
713→ '<div class="detail-row"><span class="detail-label">Session ID</span><span class="detail-value">' + m.id.slice(0,8) + '...</span></div>' +
|
||||
714→ '<div class="detail-row"><span class="detail-label">Connected</span><span class="detail-value">' + started + '</span></div>' +
|
||||
715→ '<div class="detail-row"><span class="detail-label">Viewers</span><span class="detail-value">' + m.viewer_count + '</span></div>' +
|
||||
716→ '</div>' +
|
||||
717→ '<div class="detail-section">' +
|
||||
718→ '<div class="detail-section-title">Actions</div>' +
|
||||
719→ '<button class="btn btn-primary" style="width: 100%; margin-bottom: 8px;" onclick="connectToMachine(\'' + m.id + '\')" ' + connectDisabled + ' ' + connectTitle + '>Connect</button>' +
|
||||
720→ '<button class="btn btn-outline" style="width: 100%; margin-bottom: 8px;" onclick="openChat(\'' + m.id + '\', \'' + (m.agent_name || 'Client').replace(/'/g, "\\'") + '\')" ' + connectDisabled + '>Chat</button>' +
|
||||
721→ '<button class="btn btn-outline" style="width: 100%; margin-bottom: 8px;" disabled>Transfer Files</button>' +
|
||||
722→ '<button class="btn btn-outline" style="width: 100%; color: hsl(0, 62.8%, 50%);" onclick="disconnectMachine(\'' + m.id + '\', \'' + (m.agent_name || m.agent_id).replace(/'/g, "\\'") + '\')">Disconnect</button>' +
|
||||
723→ '</div>';
|
||||
724→ }
|
||||
725→
|
||||
726→ function connectToMachine(sessionId) {
|
||||
727→ // Open viewer in new window
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 127
|
||||
bash: line 1: cargo: command not found
|
||||
@@ -0,0 +1,28 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\api\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
26→ pub uptime_secs: i64,
|
||||
27→ pub display_count: i32,
|
||||
28→}
|
||||
29→
|
||||
30→impl From<crate::session::Session> for SessionInfo {
|
||||
31→ fn from(s: crate::session::Session) -> Self {
|
||||
32→ Self {
|
||||
33→ id: s.id.to_string(),
|
||||
34→ agent_id: s.agent_id,
|
||||
35→ agent_name: s.agent_name,
|
||||
36→ started_at: s.started_at.to_rfc3339(),
|
||||
37→ viewer_count: s.viewer_count,
|
||||
38→ is_streaming: s.is_streaming,
|
||||
39→ is_online: s.is_online,
|
||||
40→ is_persistent: s.is_persistent,
|
||||
41→ last_heartbeat: s.last_heartbeat.to_rfc3339(),
|
||||
42→ os_version: s.os_version,
|
||||
43→ is_elevated: s.is_elevated,
|
||||
44→ uptime_secs: s.uptime_secs,
|
||||
45→ display_count: s.display_count,
|
||||
46→ }
|
||||
47→ }
|
||||
48→}
|
||||
49→
|
||||
50→/// List all active sessions
|
||||
51→pub async fn list_sessions(
|
||||
52→ State(sessions): State<SessionManager>,
|
||||
@@ -0,0 +1,328 @@
|
||||
1→//! WebSocket relay handlers
|
||||
2→//!
|
||||
3→//! Handles WebSocket connections from agents and viewers,
|
||||
4→//! relaying video frames and input events between them.
|
||||
5→
|
||||
6→use axum::{
|
||||
7→ extract::{
|
||||
8→ ws::{Message, WebSocket, WebSocketUpgrade},
|
||||
9→ Query, State,
|
||||
10→ },
|
||||
11→ response::IntoResponse,
|
||||
12→};
|
||||
13→use futures_util::{SinkExt, StreamExt};
|
||||
14→use prost::Message as ProstMessage;
|
||||
15→use serde::Deserialize;
|
||||
16→use tracing::{error, info, warn};
|
||||
17→use uuid::Uuid;
|
||||
18→
|
||||
19→use crate::proto;
|
||||
20→use crate::session::SessionManager;
|
||||
21→use crate::AppState;
|
||||
22→
|
||||
23→#[derive(Debug, Deserialize)]
|
||||
24→pub struct AgentParams {
|
||||
25→ agent_id: String,
|
||||
26→ #[serde(default)]
|
||||
27→ agent_name: Option<String>,
|
||||
28→ #[serde(default)]
|
||||
29→ support_code: Option<String>,
|
||||
30→ #[serde(default)]
|
||||
31→ hostname: Option<String>,
|
||||
32→}
|
||||
33→
|
||||
34→#[derive(Debug, Deserialize)]
|
||||
35→pub struct ViewerParams {
|
||||
36→ session_id: String,
|
||||
37→}
|
||||
38→
|
||||
39→/// WebSocket handler for agent connections
|
||||
40→pub async fn agent_ws_handler(
|
||||
41→ ws: WebSocketUpgrade,
|
||||
42→ State(state): State<AppState>,
|
||||
43→ Query(params): Query<AgentParams>,
|
||||
44→) -> impl IntoResponse {
|
||||
45→ let agent_id = params.agent_id;
|
||||
46→ let agent_name = params.hostname.or(params.agent_name).unwrap_or_else(|| agent_id.clone());
|
||||
47→ let support_code = params.support_code;
|
||||
48→ let sessions = state.sessions.clone();
|
||||
49→ let support_codes = state.support_codes.clone();
|
||||
50→
|
||||
51→ ws.on_upgrade(move |socket| handle_agent_connection(socket, sessions, support_codes, agent_id, agent_name, support_code))
|
||||
52→}
|
||||
53→
|
||||
54→/// WebSocket handler for viewer connections
|
||||
55→pub async fn viewer_ws_handler(
|
||||
56→ ws: WebSocketUpgrade,
|
||||
57→ State(state): State<AppState>,
|
||||
58→ Query(params): Query<ViewerParams>,
|
||||
59→) -> impl IntoResponse {
|
||||
60→ let session_id = params.session_id;
|
||||
61→ let sessions = state.sessions.clone();
|
||||
62→
|
||||
63→ ws.on_upgrade(move |socket| handle_viewer_connection(socket, sessions, session_id))
|
||||
64→}
|
||||
65→
|
||||
66→/// Handle an agent WebSocket connection
|
||||
67→async fn handle_agent_connection(
|
||||
68→ socket: WebSocket,
|
||||
69→ sessions: SessionManager,
|
||||
70→ support_codes: crate::support_codes::SupportCodeManager,
|
||||
71→ agent_id: String,
|
||||
72→ agent_name: String,
|
||||
73→ support_code: Option<String>,
|
||||
74→) {
|
||||
75→ info!("Agent connected: {} ({})", agent_name, agent_id);
|
||||
76→
|
||||
77→ let (mut ws_sender, mut ws_receiver) = socket.split();
|
||||
78→
|
||||
79→ // If a support code was provided, check if it's valid
|
||||
80→ if let Some(ref code) = support_code {
|
||||
81→ // Check if the code is cancelled or invalid
|
||||
82→ if support_codes.is_cancelled(code).await {
|
||||
83→ warn!("Agent tried to connect with cancelled code: {}", code);
|
||||
84→ // Send disconnect message to agent
|
||||
85→ let disconnect_msg = proto::Message {
|
||||
86→ payload: Some(proto::message::Payload::Disconnect(proto::Disconnect {
|
||||
87→ reason: "Support session was cancelled by technician".to_string(),
|
||||
88→ })),
|
||||
89→ };
|
||||
90→ let mut buf = Vec::new();
|
||||
91→ if prost::Message::encode(&disconnect_msg, &mut buf).is_ok() {
|
||||
92→ let _ = ws_sender.send(Message::Binary(buf.into())).await;
|
||||
93→ }
|
||||
94→ let _ = ws_sender.close().await;
|
||||
95→ return;
|
||||
96→ }
|
||||
97→ }
|
||||
98→
|
||||
99→ // Register the agent and get channels
|
||||
100→ let (session_id, frame_tx, mut input_rx) = sessions.register_agent(agent_id.clone(), agent_name.clone()).await;
|
||||
101→
|
||||
102→ info!("Session created: {} (agent in idle mode)", session_id);
|
||||
103→
|
||||
104→ // If a support code was provided, mark it as connected
|
||||
105→ if let Some(ref code) = support_code {
|
||||
106→ info!("Linking support code {} to session {}", code, session_id);
|
||||
107→ support_codes.mark_connected(code, Some(agent_name.clone()), Some(agent_id.clone())).await;
|
||||
108→ support_codes.link_session(code, session_id).await;
|
||||
109→ }
|
||||
110→
|
||||
111→ // Use Arc<Mutex> for sender so we can use it from multiple places
|
||||
112→ let ws_sender = std::sync::Arc::new(tokio::sync::Mutex::new(ws_sender));
|
||||
113→ let ws_sender_input = ws_sender.clone();
|
||||
114→ let ws_sender_cancel = ws_sender.clone();
|
||||
115→
|
||||
116→ // Task to forward input events from viewers to agent
|
||||
117→ let input_forward = tokio::spawn(async move {
|
||||
118→ while let Some(input_data) = input_rx.recv().await {
|
||||
119→ let mut sender = ws_sender_input.lock().await;
|
||||
120→ if sender.send(Message::Binary(input_data.into())).await.is_err() {
|
||||
121→ break;
|
||||
122→ }
|
||||
123→ }
|
||||
124→ });
|
||||
125→
|
||||
126→ let sessions_cleanup = sessions.clone();
|
||||
127→ let sessions_status = sessions.clone();
|
||||
128→ let support_codes_cleanup = support_codes.clone();
|
||||
129→ let support_code_cleanup = support_code.clone();
|
||||
130→ let support_code_check = support_code.clone();
|
||||
131→ let support_codes_check = support_codes.clone();
|
||||
132→
|
||||
133→ // Task to check for cancellation every 2 seconds
|
||||
134→ let cancel_check = tokio::spawn(async move {
|
||||
135→ let mut interval = tokio::time::interval(std::time::Duration::from_secs(2));
|
||||
136→ loop {
|
||||
137→ interval.tick().await;
|
||||
138→ if let Some(ref code) = support_code_check {
|
||||
139→ if support_codes_check.is_cancelled(code).await {
|
||||
140→ info!("Support code {} was cancelled, disconnecting agent", code);
|
||||
141→ // Send disconnect message
|
||||
142→ let disconnect_msg = proto::Message {
|
||||
143→ payload: Some(proto::message::Payload::Disconnect(proto::Disconnect {
|
||||
144→ reason: "Support session was cancelled by technician".to_string(),
|
||||
145→ })),
|
||||
146→ };
|
||||
147→ let mut buf = Vec::new();
|
||||
148→ if prost::Message::encode(&disconnect_msg, &mut buf).is_ok() {
|
||||
149→ let mut sender = ws_sender_cancel.lock().await;
|
||||
150→ let _ = sender.send(Message::Binary(buf.into())).await;
|
||||
151→ let _ = sender.close().await;
|
||||
152→ }
|
||||
153→ break;
|
||||
154→ }
|
||||
155→ }
|
||||
156→ }
|
||||
157→ });
|
||||
158→
|
||||
159→ // Main loop: receive messages from agent
|
||||
160→ while let Some(msg) = ws_receiver.next().await {
|
||||
161→ match msg {
|
||||
162→ Ok(Message::Binary(data)) => {
|
||||
163→ // Try to decode as protobuf message
|
||||
164→ match proto::Message::decode(data.as_ref()) {
|
||||
165→ Ok(proto_msg) => {
|
||||
166→ match &proto_msg.payload {
|
||||
167→ Some(proto::message::Payload::VideoFrame(_)) => {
|
||||
168→ // Broadcast frame to all viewers (only sent when streaming)
|
||||
169→ let _ = frame_tx.send(data.to_vec());
|
||||
170→ }
|
||||
171→ Some(proto::message::Payload::ChatMessage(chat)) => {
|
||||
172→ // Broadcast chat message to all viewers
|
||||
173→ info!("Chat from client: {}", chat.content);
|
||||
174→ let _ = frame_tx.send(data.to_vec());
|
||||
175→ }
|
||||
176→ Some(proto::message::Payload::AgentStatus(status)) => {
|
||||
177→ // Update session with agent status
|
||||
178→ sessions_status.update_agent_status(
|
||||
179→ session_id,
|
||||
180→ Some(status.os_version.clone()),
|
||||
181→ status.is_elevated,
|
||||
182→ status.uptime_secs,
|
||||
183→ status.display_count,
|
||||
184→ status.is_streaming,
|
||||
185→ ).await;
|
||||
186→ info!("Agent status update: {} - streaming={}, uptime={}s",
|
||||
187→ status.hostname, status.is_streaming, status.uptime_secs);
|
||||
188→ }
|
||||
189→ Some(proto::message::Payload::Heartbeat(_)) => {
|
||||
190→ // Update heartbeat timestamp
|
||||
191→ sessions_status.update_heartbeat(session_id).await;
|
||||
192→ }
|
||||
193→ Some(proto::message::Payload::HeartbeatAck(_)) => {
|
||||
194→ // Agent acknowledged our heartbeat
|
||||
195→ sessions_status.update_heartbeat(session_id).await;
|
||||
196→ }
|
||||
197→ _ => {}
|
||||
198→ }
|
||||
199→ }
|
||||
200→ Err(e) => {
|
||||
201→ warn!("Failed to decode agent message: {}", e);
|
||||
202→ }
|
||||
203→ }
|
||||
204→ }
|
||||
205→ Ok(Message::Close(_)) => {
|
||||
206→ info!("Agent disconnected: {}", agent_id);
|
||||
207→ break;
|
||||
208→ }
|
||||
209→ Ok(Message::Ping(data)) => {
|
||||
210→ // Pong is handled automatically by axum
|
||||
211→ let _ = data;
|
||||
212→ }
|
||||
213→ Ok(_) => {}
|
||||
214→ Err(e) => {
|
||||
215→ error!("WebSocket error from agent {}: {}", agent_id, e);
|
||||
216→ break;
|
||||
217→ }
|
||||
218→ }
|
||||
219→ }
|
||||
220→
|
||||
221→ // Cleanup
|
||||
222→ input_forward.abort();
|
||||
223→ cancel_check.abort();
|
||||
224→ sessions_cleanup.remove_session(session_id).await;
|
||||
225→
|
||||
226→ // Mark support code as completed if one was used (unless cancelled)
|
||||
227→ if let Some(ref code) = support_code_cleanup {
|
||||
228→ if !support_codes_cleanup.is_cancelled(code).await {
|
||||
229→ support_codes_cleanup.mark_completed(code).await;
|
||||
230→ info!("Support code {} marked as completed", code);
|
||||
231→ }
|
||||
232→ }
|
||||
233→
|
||||
234→ info!("Session {} ended", session_id);
|
||||
235→}
|
||||
236→
|
||||
237→/// Handle a viewer WebSocket connection
|
||||
238→async fn handle_viewer_connection(
|
||||
239→ socket: WebSocket,
|
||||
240→ sessions: SessionManager,
|
||||
241→ session_id_str: String,
|
||||
242→) {
|
||||
243→ // Parse session ID
|
||||
244→ let session_id = match uuid::Uuid::parse_str(&session_id_str) {
|
||||
245→ Ok(id) => id,
|
||||
246→ Err(_) => {
|
||||
247→ warn!("Invalid session ID: {}", session_id_str);
|
||||
248→ return;
|
||||
249→ }
|
||||
250→ };
|
||||
251→
|
||||
252→ // Generate unique viewer ID
|
||||
253→ let viewer_id = Uuid::new_v4().to_string();
|
||||
254→
|
||||
255→ // Join the session (this sends StartStream to agent if first viewer)
|
||||
256→ let (mut frame_rx, input_tx) = match sessions.join_session(session_id, viewer_id.clone()).await {
|
||||
257→ Some(channels) => channels,
|
||||
258→ None => {
|
||||
259→ warn!("Session not found: {}", session_id);
|
||||
260→ return;
|
||||
261→ }
|
||||
262→ };
|
||||
263→
|
||||
264→ info!("Viewer {} joined session: {}", viewer_id, session_id);
|
||||
265→
|
||||
266→ let (mut ws_sender, mut ws_receiver) = socket.split();
|
||||
267→
|
||||
268→ // Task to forward frames from agent to this viewer
|
||||
269→ let frame_forward = tokio::spawn(async move {
|
||||
270→ while let Ok(frame_data) = frame_rx.recv().await {
|
||||
271→ if ws_sender.send(Message::Binary(frame_data.into())).await.is_err() {
|
||||
272→ break;
|
||||
273→ }
|
||||
274→ }
|
||||
275→ });
|
||||
276→
|
||||
277→ let sessions_cleanup = sessions.clone();
|
||||
278→ let viewer_id_cleanup = viewer_id.clone();
|
||||
279→
|
||||
280→ // Main loop: receive input from viewer and forward to agent
|
||||
281→ while let Some(msg) = ws_receiver.next().await {
|
||||
282→ match msg {
|
||||
283→ Ok(Message::Binary(data)) => {
|
||||
284→ // Try to decode as protobuf message
|
||||
285→ match proto::Message::decode(data.as_ref()) {
|
||||
286→ Ok(proto_msg) => {
|
||||
287→ match &proto_msg.payload {
|
||||
288→ Some(proto::message::Payload::MouseEvent(_)) |
|
||||
289→ Some(proto::message::Payload::KeyEvent(_)) |
|
||||
290→ Some(proto::message::Payload::SpecialKey(_)) => {
|
||||
291→ // Forward input to agent
|
||||
292→ let _ = input_tx.send(data.to_vec()).await;
|
||||
293→ }
|
||||
294→ Some(proto::message::Payload::ChatMessage(chat)) => {
|
||||
295→ // Forward chat message to agent
|
||||
296→ info!("Chat from technician: {}", chat.content);
|
||||
297→ let _ = input_tx.send(data.to_vec()).await;
|
||||
298→ }
|
||||
299→ _ => {}
|
||||
300→ }
|
||||
301→ }
|
||||
302→ Err(e) => {
|
||||
303→ warn!("Failed to decode viewer message: {}", e);
|
||||
304→ }
|
||||
305→ }
|
||||
306→ }
|
||||
307→ Ok(Message::Close(_)) => {
|
||||
308→ info!("Viewer {} disconnected from session: {}", viewer_id, session_id);
|
||||
309→ break;
|
||||
310→ }
|
||||
311→ Ok(_) => {}
|
||||
312→ Err(e) => {
|
||||
313→ error!("WebSocket error from viewer {}: {}", viewer_id, e);
|
||||
314→ break;
|
||||
315→ }
|
||||
316→ }
|
||||
317→ }
|
||||
318→
|
||||
319→ // Cleanup (this sends StopStream to agent if last viewer)
|
||||
320→ frame_forward.abort();
|
||||
321→ sessions_cleanup.leave_session(session_id, &viewer_id_cleanup).await;
|
||||
322→ info!("Viewer {} left session: {}", viewer_id_cleanup, session_id);
|
||||
323→}
|
||||
324→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,16 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\main.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
206→ .route("/api/sessions", get(list_sessions))
|
||||
207→ .route("/api/sessions/:id", get(get_session))
|
||||
208→ .route("/api/sessions/:id", delete(disconnect_session))
|
||||
209→
|
||||
210→ // REST API - Machines
|
||||
211→ .route("/api/machines", get(list_machines))
|
||||
212→ .route("/api/machines/:agent_id", get(get_machine))
|
||||
213→ .route("/api/machines/:agent_id", delete(delete_machine))
|
||||
214→ .route("/api/machines/:agent_id/history", get(get_machine_history))
|
||||
215→
|
||||
216→ // HTML page routes (clean URLs)
|
||||
217→ .route("/login", get(serve_login))
|
||||
218→ .route("/dashboard", get(serve_dashboard))
|
||||
219→ .route("/users", get(serve_users))
|
||||
220→
|
||||
@@ -0,0 +1 @@
|
||||
Created start-server.sh
|
||||
@@ -0,0 +1 @@
|
||||
ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIABnQjolTxDtfqOwdDjamK1oyFPiQnaNT/tAgsIHH1Zo claude-code
|
||||
@@ -0,0 +1,19 @@
|
||||
Get-Content : Cannot find path 'C:\Users\MikeSwanson\Claude\-' because it does not exist.
|
||||
At line:1 char:18
|
||||
+ & { \:SSHPASS = (Get-Content -Raw -Path '-'); ssh root@172.16.3.20 'e ...
|
||||
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : ObjectNotFound: (C:\Users\MikeSwanson\Claude\-:String) [Get-Content], ItemNotFoundExcept
|
||||
ion
|
||||
+ FullyQualifiedErrorId : PathNotFound,Microsoft.PowerShell.Commands.GetContentCommand
|
||||
|
||||
\:SSHPASS : The term '\:SSHPASS' is not recognized as the name of a cmdlet, function, script file, or operable
|
||||
program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.
|
||||
At line:1 char:5
|
||||
+ & { \:SSHPASS = (Get-Content -Raw -Path '-'); ssh root@172.16.3.20 'e ...
|
||||
+ ~~~~~~~~~
|
||||
+ CategoryInfo : ObjectNotFound: (\:SSHPASS:String) [], CommandNotFoundException
|
||||
+ FullyQualifiedErrorId : CommandNotFoundException
|
||||
|
||||
Permission denied, please try again.
|
||||
Permission denied, please try again.
|
||||
root@172.16.3.20: Permission denied (publickey,password,keyboard-interactive).
|
||||
@@ -0,0 +1,44 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\session\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
286→ let _ = session_data.input_tx.send(buf).await;
|
||||
287→ }
|
||||
288→ }
|
||||
289→
|
||||
290→ /// Mark agent as disconnected
|
||||
291→ /// For persistent agents: keep session but mark as offline
|
||||
292→ /// For support sessions: remove session entirely
|
||||
293→ pub async fn mark_agent_disconnected(&self, session_id: SessionId) {
|
||||
294→ let mut sessions = self.sessions.write().await;
|
||||
295→ if let Some(session_data) = sessions.get_mut(&session_id) {
|
||||
296→ if session_data.info.is_persistent {
|
||||
297→ // Persistent agent - keep session but mark as offline
|
||||
298→ tracing::info!("Persistent agent {} marked offline (session {} preserved)",
|
||||
299→ session_data.info.agent_id, session_id);
|
||||
300→ session_data.info.is_online = false;
|
||||
301→ session_data.info.is_streaming = false;
|
||||
302→ session_data.info.viewer_count = 0;
|
||||
303→ session_data.viewers.clear();
|
||||
304→ } else {
|
||||
305→ // Support session - remove entirely
|
||||
306→ let agent_id = session_data.info.agent_id.clone();
|
||||
307→ sessions.remove(&session_id);
|
||||
308→ drop(sessions); // Release sessions lock before acquiring agents lock
|
||||
309→ let mut agents = self.agents.write().await;
|
||||
310→ agents.remove(&agent_id);
|
||||
311→ tracing::info!("Support session {} removed", session_id);
|
||||
312→ }
|
||||
313→ }
|
||||
314→ }
|
||||
315→
|
||||
316→ /// Remove a session entirely (for cleanup)
|
||||
317→ pub async fn remove_session(&self, session_id: SessionId) {
|
||||
318→ let mut sessions = self.sessions.write().await;
|
||||
319→ if let Some(session_data) = sessions.remove(&session_id) {
|
||||
320→ drop(sessions);
|
||||
321→ let mut agents = self.agents.write().await;
|
||||
322→ agents.remove(&session_data.info.agent_id);
|
||||
323→ }
|
||||
324→ }
|
||||
325→
|
||||
326→ /// Disconnect a session by sending a disconnect message to the agent
|
||||
327→ /// Returns true if the message was sent successfully
|
||||
328→ pub async fn disconnect_session(&self, session_id: SessionId, reason: &str) -> bool {
|
||||
@@ -0,0 +1 @@
|
||||
Command running in background with ID: b65271c. Output is being written to: C:\Users\MIKESW~1\AppData\Local\Temp\claude\C--Users-MikeSwanson-Claude\tasks\b65271c.output
|
||||
@@ -0,0 +1,13 @@
|
||||
Starting GuruConnect Server...
|
||||
Server log:
|
||||
[2m2025-12-30T04:07:27.976024Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Connecting to database...
|
||||
[2m2025-12-30T04:07:27.988213Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Database connection established
|
||||
[2m2025-12-30T04:07:27.988233Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Running database migrations...
|
||||
[2m2025-12-30T04:07:27.990753Z[0m [32m INFO[0m [2msqlx::postgres::notice[0m[2m:[0m relation "_sqlx_migrations" already exists, skipping
|
||||
[2m2025-12-30T04:07:27.994265Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Migrations complete
|
||||
[2m2025-12-30T04:07:28.006350Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m 1 user(s) in database
|
||||
[2m2025-12-30T04:07:28.008802Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m Restoring 2 persistent machines from database
|
||||
[2m2025-12-30T04:07:28.008863Z[0m [32m INFO[0m [2mguruconnect_server::session[0m[2m:[0m Restored offline machine: DESKTOP-N9MIFGD (62feaeb4-42b6-47a9-8d2c-a4629a50e053)
|
||||
[2m2025-12-30T04:07:28.008877Z[0m [32m INFO[0m [2mguruconnect_server::session[0m[2m:[0m Restored offline machine: DESKTOP-N9MIFGD (99432392-c89c-4f11-8547-fa38527c75ab)
|
||||
[2m2025-12-30T04:07:28.009332Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m Server listening on 0.0.0.0:3002
|
||||
Server started. PID: 47901
|
||||
@@ -0,0 +1,23 @@
|
||||
Exit code 101
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: /tmp/guru-connect/agent/Cargo.toml
|
||||
workspace: /tmp/guru-connect/Cargo.toml
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: /tmp/guru-connect/server/Cargo.toml
|
||||
workspace: /tmp/guru-connect/Cargo.toml
|
||||
Compiling proc-macro2 v1.0.103
|
||||
Compiling quote v1.0.42
|
||||
Compiling unicode-ident v1.0.22
|
||||
Compiling serde_core v1.0.228
|
||||
Compiling serde v1.0.228
|
||||
Compiling cfg-if v1.0.4
|
||||
Compiling typenum v1.19.0
|
||||
Compiling version_check v0.9.5
|
||||
error[E0463]: can't find crate for `core`
|
||||
|
|
||||
= note: the `x86_64-pc-windows-msvc` target may not be installed
|
||||
= help: consider downloading the target with `rustup target add x86_64-pc-windows-msvc`
|
||||
|
||||
For more information about this error, try `rustc --explain E0463`.
|
||||
error: could not compile `cfg-if` (lib) due to 1 previous error
|
||||
warning: build failed, waiting for other jobs to finish...
|
||||
@@ -0,0 +1,595 @@
|
||||
1→# Session Log: 2025-12-29
|
||||
2→
|
||||
3→## Session Summary
|
||||
4→
|
||||
5→### Work Completed
|
||||
6→
|
||||
7→1. **Fixed settings.local.json**
|
||||
8→ - Removed hardcoded credentials from permission entries (exposed passwords)
|
||||
9→ - Removed redundant WebFetch domain entries (domain:* covers all)
|
||||
10→ - Consolidated specific commands to wildcard patterns
|
||||
11→ - Cleaned up invalid/obsolete entries
|
||||
12→
|
||||
13→2. **Installed GuruRMM Agent on Scileppi RS2212+ NAS**
|
||||
14→ - Agent binary already present at `/volume1/gururmm/gururmm-agent`
|
||||
15→ - Fixed config format (was `server_url`, needed `[server] url`)
|
||||
16→ - Renamed `config.toml` to `agent.toml` (agent default)
|
||||
17→ - Cleared 199MB log file (caused by interactive mode prompt loop)
|
||||
18→ - Agent successfully connected and registered
|
||||
19→
|
||||
20→3. **Network Connectivity Troubleshooting**
|
||||
21→ - External URL `wss://rmm-api.azcomputerguru.com/ws` failed from NAS
|
||||
22→ - Cause: NAT hairpin - NAS currently on same local network as server
|
||||
23→ - Tested internal URL `ws://172.16.3.30:3001/ws` - works
|
||||
24→ - Final config uses external URL for when NAS moves to Scileppi location
|
||||
25→
|
||||
26→4. **Agent Registration Confirmed**
|
||||
27→ - Agent ID: `2585f6d5-3887-412e-a586-1dec030f0a40`
|
||||
28→ - Hostname: SL-SERVER
|
||||
29→ - Client: Scileppi Law Firm
|
||||
30→ - Site: Main Office
|
||||
31→ - Status: Online (when using internal URL)
|
||||
32→
|
||||
33→---
|
||||
34→
|
||||
35→## Credentials
|
||||
36→
|
||||
37→### Scileppi RS2212+ NAS
|
||||
38→- **IP:** 172.16.1.59
|
||||
39→- **Hostname:** SL-SERVER
|
||||
40→- **SSH User:** sysadmin
|
||||
41→- **Password:** Gptf*77ttb123!@#-sl-server
|
||||
42→- **Storage:** 25TB total, 6.9TB used (28%)
|
||||
43→
|
||||
44→### Scileppi DS214se (Source - Migration Complete)
|
||||
45→- **IP:** 172.16.1.54
|
||||
46→- **SSH User:** admin
|
||||
47→- **Password:** Th1nk3r^99
|
||||
48→
|
||||
49→### Scileppi Unraid (Source - Migration Complete)
|
||||
50→- **IP:** 172.16.1.21
|
||||
51→- **SSH User:** root
|
||||
52→- **Password:** Th1nk3r^99
|
||||
53→
|
||||
54→### GuruRMM Agent on RS2212+
|
||||
55→- **API Key:** grmm_YlqtkrCpEe0Fxfc7lipqqFO_JwUUvojH
|
||||
56→- **Server URL:** wss://rmm-api.azcomputerguru.com/ws
|
||||
57→- **Agent ID:** 2585f6d5-3887-412e-a586-1dec030f0a40
|
||||
58→
|
||||
59→### Build Server (172.16.3.30)
|
||||
60→- **User:** guru
|
||||
61→- **Password:** Gptf*77ttb123!@#-rmm
|
||||
62→- **Root SSH:** Key-based auth configured
|
||||
63→
|
||||
64→### GuruRMM Server
|
||||
65→- **Internal URL:** http://172.16.3.30:3001
|
||||
66→- **External URL:** https://rmm-api.azcomputerguru.com
|
||||
67→- **WebSocket:** wss://rmm-api.azcomputerguru.com/ws
|
||||
68→- **DNS:** Resolves to 72.194.62.4
|
||||
69→
|
||||
70→---
|
||||
71→
|
||||
72→## Infrastructure
|
||||
73→
|
||||
74→### SSH Access to RS2212+ (via jump host)
|
||||
75→```bash
|
||||
76→ssh guru@172.16.3.30 << 'ENDSSH'
|
||||
77→cat > /tmp/synopass << 'PASSEOF'
|
||||
78→Gptf*77ttb123!@#-sl-server
|
||||
79→PASSEOF
|
||||
80→sshpass -f /tmp/synopass ssh -o StrictHostKeyChecking=no sysadmin@172.16.1.59 "command"
|
||||
81→rm -f /tmp/synopass
|
||||
82→ENDSSH
|
||||
83→```
|
||||
84→
|
||||
85→### Files on RS2212+
|
||||
86→```
|
||||
87→/volume1/gururmm/
|
||||
88→├── gururmm-agent (4.7 MB binary, v0.5.1)
|
||||
89→├── agent.toml (config file)
|
||||
90→├── agent.log (log file)
|
||||
91→└── start.sh (startup script)
|
||||
92→```
|
||||
93→
|
||||
94→### Agent Config (/volume1/gururmm/agent.toml)
|
||||
95→```toml
|
||||
96→[server]
|
||||
97→url = "wss://rmm-api.azcomputerguru.com/ws"
|
||||
98→api_key = "grmm_YlqtkrCpEe0Fxfc7lipqqFO_JwUUvojH"
|
||||
99→
|
||||
100→[metrics]
|
||||
101→interval_seconds = 60
|
||||
102→collect_cpu = true
|
||||
103→collect_memory = true
|
||||
104→collect_disk = true
|
||||
105→collect_network = true
|
||||
106→
|
||||
107→[watchdog]
|
||||
108→enabled = false
|
||||
109→check_interval_seconds = 30
|
||||
110→```
|
||||
111→
|
||||
112→### Startup Script (/volume1/gururmm/start.sh)
|
||||
113→```bash
|
||||
114→#!/bin/bash
|
||||
115→cd /volume1/gururmm
|
||||
116→nohup ./gururmm-agent run >> agent.log 2>&1 &
|
||||
117→```
|
||||
118→
|
||||
119→---
|
||||
120→
|
||||
121→## Commands Reference
|
||||
122→
|
||||
123→### Agent CLI
|
||||
124→```bash
|
||||
125→gururmm-agent run # Run the agent
|
||||
126→gururmm-agent setup # Interactive setup
|
||||
127→gururmm-agent install # Install as system service
|
||||
128→gururmm-agent status # Show agent status
|
||||
129→gururmm-agent generate-config # Generate sample config
|
||||
130→gururmm-agent -c config.toml run # Run with specific config
|
||||
131→```
|
||||
132→
|
||||
133→### Check Agent Status
|
||||
134→```bash
|
||||
135→# View log
|
||||
136→tail -f /volume1/gururmm/agent.log
|
||||
137→
|
||||
138→# Check if running
|
||||
139→ps aux | grep gururmm-agent
|
||||
140→
|
||||
141→# Kill agent
|
||||
142→pkill gururmm-agent
|
||||
143→
|
||||
144→# Start agent
|
||||
145→cd /volume1/gururmm && nohup ./gururmm-agent run >> agent.log 2>&1 &
|
||||
146→```
|
||||
147→
|
||||
148→### Check RMM Dashboard Agents
|
||||
149→```bash
|
||||
150→curl -s http://172.16.3.30:3001/api/agents | python3 -m json.tool
|
||||
151→```
|
||||
152→
|
||||
153→---
|
||||
154→
|
||||
155→## Problems Encountered & Solutions
|
||||
156→
|
||||
157→### 1. Config Format Wrong
|
||||
158→- **Problem:** Agent expected `[server] url = ...` format, had `server_url = ...`
|
||||
159→- **Solution:** Generated sample config with `gururmm-agent generate-config`, matched format
|
||||
160→
|
||||
161→### 2. 199MB Log File
|
||||
162→- **Problem:** Agent ran in interactive mode, filled log with "Enter API Key" prompts
|
||||
163→- **Solution:** Renamed config.toml to agent.toml (default name), ran with `run` subcommand
|
||||
164→
|
||||
165→### 3. NAT Hairpin Issue
|
||||
166→- **Problem:** NAS couldn't reach `wss://rmm-api.azcomputerguru.com/ws`
|
||||
167→- **Cause:** NAS temporarily on same local network as server
|
||||
168→- **Solution:** Config set to external URL, will work when NAS moved to Scileppi
|
||||
169→
|
||||
170→### 4. No Sudo on Synology
|
||||
171→- **Problem:** sysadmin user can't create systemd service or rc.d script
|
||||
172→- **Solution:** Created start.sh script, manual Task Scheduler setup required in DSM
|
||||
173→
|
||||
174→---
|
||||
175→
|
||||
176→## Pending Tasks
|
||||
177→
|
||||
178→### Scileppi NAS
|
||||
179→1. **Move NAS to Scileppi location** - Agent will auto-connect via external URL
|
||||
180→2. **Set up Task Scheduler in DSM** for startup persistence:
|
||||
181→ - Control Panel → Task Scheduler → Triggered Task
|
||||
182→ - User: `sysadmin`, Event: Boot-up
|
||||
183→ - Script: `/volume1/gururmm/start.sh`
|
||||
184→
|
||||
185→### Scileppi Data Restructure (from previous session)
|
||||
186→1. Create "Data" shared folder on RS2212+
|
||||
187→2. Create user accounts (Chris, Andrew, Sylvia, Rose, +1 TBD)
|
||||
188→3. Move data from /volume1/homes/ to /volume1/Data/
|
||||
189→4. Configure SMB for Mac clients
|
||||
190→
|
||||
191→---
|
||||
192→
|
||||
193→## Reference
|
||||
194→
|
||||
195→### Current RS2212+ /volume1 Contents
|
||||
196→| Folder | Size | Source |
|
||||
197→|--------|------|--------|
|
||||
198→| Data | new | Created for restructure |
|
||||
199→| homes | 6.7TB | Contains migrated data |
|
||||
200→| gururmm | ~5MB | RMM agent |
|
||||
201→| Test | - | Test share |
|
||||
202→
|
||||
203→### Agents in GuruRMM Dashboard
|
||||
204→| Hostname | Status | Client | Agent ID |
|
||||
205→|----------|--------|--------|----------|
|
||||
206→| ACG-M-L5090 | offline | AZ Computer Guru | 97f63c3b-... |
|
||||
207→| gururmm | online | AZ Computer Guru | 8cd0440f-... |
|
||||
208→| SL-SERVER | online* | Scileppi Law Firm | 2585f6d5-... |
|
||||
209→
|
||||
210→*SL-SERVER will show offline until moved to Scileppi network (NAT hairpin)
|
||||
211→
|
||||
212→---
|
||||
213→
|
||||
214→## Update: 08:30 - NAS Prep and DNS Fix
|
||||
215→
|
||||
216→### Work Completed
|
||||
217→
|
||||
218→5. **Changed RS2212+ Bond Mode**
|
||||
219→ - Changed from LACP (mode 4) to active-backup (mode 1)
|
||||
220→ - LACP requires switch configuration; active-backup works with any switch
|
||||
221→ - Config: `/etc/sysconfig/network-scripts/ifcfg-bond0`
|
||||
222→ - New BONDING_OPTS: `mode=1 miimon=100 primary=eth0`
|
||||
223→
|
||||
224→6. **Shutdown RS2212+ for Site Move**
|
||||
225→ - NAS shut down for physical move to Scileppi location
|
||||
226→ - Will get new DHCP IP at Scileppi network
|
||||
227→ - Agent will auto-connect via external URL once powered on
|
||||
228→
|
||||
229→7. **Fixed cascadestucson.com DMARC**
|
||||
230→ - **Problem:** Duplicate DMARC records causing validation failures
|
||||
231→ - Deleted: `v=DMARC1; p=none;` (line 21)
|
||||
232→ - Kept: `v=DMARC1;p=none;pct=100;rua=mailto:info@cascadestucson.com;ruf=mailto:info@cascadestucson.com;ri=86400;fo=1;`
|
||||
233→
|
||||
234→8. **Set cascadestucson.com Zone TTL to 5 Minutes**
|
||||
235→ - Updated SOA minimum TTL to 300
|
||||
236→ - Updated all 31 records to 300 second TTL
|
||||
237→ - Faster propagation for any future changes
|
||||
238→
|
||||
239→### DNS Status - cascadestucson.com
|
||||
240→
|
||||
241→| Record | Status | Value |
|
||||
242→|--------|--------|-------|
|
||||
243→| DMARC | FIXED | Single record with reporting |
|
||||
244→| SPF | OK | `v=spf1 include:spf.protection.outlook.com -all` |
|
||||
245→| MX | OK | `cascadestucson-com.mail.protection.outlook.com` |
|
||||
246→| DKIM | OK | selector1/selector2 CNAMEs to Microsoft 365 |
|
||||
247→| TTL | 300s | All records now 5 minute TTL |
|
||||
248→
|
||||
249→### WHM API Used
|
||||
250→
|
||||
251→```bash
|
||||
252→# Delete DMARC record
|
||||
253→curl -s "https://websvr.acghosting.com:2087/json-api/removezonerecord?domain=cascadestucson.com&zone=cascadestucson.com&line=21" \
|
||||
254→ -H "Authorization: whm root:8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O"
|
||||
255→
|
||||
256→# Edit record TTL
|
||||
257→curl -s "https://websvr.acghosting.com:2087/json-api/editzonerecord?domain=cascadestucson.com&line=X&type=TXT&name=record.&ttl=300&txtdata=value" \
|
||||
258→ -H "Authorization: whm root:8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O"
|
||||
259→```
|
||||
260→
|
||||
261→---
|
||||
262→
|
||||
263→## Credentials Added
|
||||
264→
|
||||
265→### WebSvr (WHM/cPanel)
|
||||
266→- **Host:** websvr.acghosting.com
|
||||
267→- **API Token:** 8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O
|
||||
268→- **SSH User:** root
|
||||
269→- **SSH Password:** r3tr0gradE99#
|
||||
270→
|
||||
271→---
|
||||
272→
|
||||
273→## Update: 09:30 - Scileppi NAS Deployment Complete
|
||||
274→
|
||||
275→### Work Completed
|
||||
276→
|
||||
277→9. **NAS Online at Scileppi Location**
|
||||
278→ - New IP: 192.168.242.5 (via VPN)
|
||||
279→ - RMM agent connected successfully to `wss://rmm-api.azcomputerguru.com/ws`
|
||||
280→ - Agent status: **online** in dashboard
|
||||
281→
|
||||
282→10. **Fixed Filename Colon Issue**
|
||||
283→ - 6,505 files and 122 folders had ":" in names (Mac "/" → ":" translation)
|
||||
284→ - Renamed all to use "-" instead for Windows compatibility
|
||||
285→ - Command: `find /volume1/Data -name '*:*' | while read f; do mv "$f" "$(echo $f | sed 's/:/-/g')"; done`
|
||||
286→
|
||||
287→11. **Configured Agent Startup Persistence**
|
||||
288→ - Created `/usr/local/etc/rc.d/S99gururmm.sh`
|
||||
289→ - Agent will auto-start on boot
|
||||
290→ - SSH as root works: `root@192.168.242.5` with same password
|
||||
291→
|
||||
292→### Scileppi NAS Final Configuration
|
||||
293→
|
||||
294→| Setting | Value |
|
||||
295→|---------|-------|
|
||||
296→| IP | 192.168.242.5 |
|
||||
297→| Hostname | SL-SERVER |
|
||||
298→| SSH User | sysadmin / root |
|
||||
299→| Password | Gptf*77ttb123!@#-sl-server |
|
||||
300→| Bond Mode | active-backup (mode 1) |
|
||||
301→| RMM Agent | /volume1/gururmm/gururmm-agent |
|
||||
302→| Startup Script | /usr/local/etc/rc.d/S99gururmm.sh |
|
||||
303→| Agent Status | Online |
|
||||
304→
|
||||
305→### Startup Script (/usr/local/etc/rc.d/S99gururmm.sh)
|
||||
306→```bash
|
||||
307→#!/bin/sh
|
||||
308→case "$1" in
|
||||
309→ start)
|
||||
310→ if [ -x /volume1/gururmm/gururmm-agent ]; then
|
||||
311→ cd /volume1/gururmm
|
||||
312→ /volume1/gururmm/gururmm-agent run >> /volume1/gururmm/agent.log 2>&1 &
|
||||
313→ echo "GuruRMM Agent started"
|
||||
314→ fi
|
||||
315→ ;;
|
||||
316→ stop)
|
||||
317→ pkill -f gururmm-agent
|
||||
318→ echo "GuruRMM Agent stopped"
|
||||
319→ ;;
|
||||
320→esac
|
||||
321→exit 0
|
||||
322→```
|
||||
323→
|
||||
324→---
|
||||
325→
|
||||
326→## Future Feature: RMM Agent Tunnel/Proxy
|
||||
327→
|
||||
328→**Concept:** Create direct TCP tunnels through RMM agents to access remote services without VPN.
|
||||
329→
|
||||
330→**Use case:** Access Synology DSM (port 5000) at Scileppi via tunnel through RMM agents.
|
||||
331→
|
||||
332→**Architecture:**
|
||||
333→```
|
||||
334→Browser → localhost:15000 → Local Agent → RMM Server → Remote Agent → DSM:5000
|
||||
335→```
|
||||
336→
|
||||
337→**Implementation approach:** WebSocket relay (uses existing agent connections)
|
||||
338→
|
||||
339→**Status:** Pending - return to this later
|
||||
340→
|
||||
341→---
|
||||
342→
|
||||
343→## Git Status
|
||||
344→- Modified: `.claude/settings.local.json` (cleaned up)
|
||||
345→- Added: `session-logs/2025-12-29-session.md`
|
||||
346→
|
||||
347→---
|
||||
348→
|
||||
349→## Update: 12:45 - M365 Investigation Tools & DNS Fixes
|
||||
350→
|
||||
351→### Work Completed
|
||||
352→
|
||||
353→1. **Created Claude-MSP-Access Multi-Tenant App**
|
||||
354→ - Full Graph API access for M365 investigations and remediation
|
||||
355→ - Permissions: User.ReadWrite.All, Directory.ReadWrite.All, Mail.ReadWrite, MailboxSettings.ReadWrite, AuditLog.Read.All, Application.ReadWrite.All, DelegatedPermissionGrant.ReadWrite.All, Group.ReadWrite.All, SecurityEvents.ReadWrite.All, AppRoleAssignment.ReadWrite.All, UserAuthenticationMethod.ReadWrite.All
|
||||
356→ - Admin consent URL for onboarding new tenants
|
||||
357→
|
||||
358→2. **Tested on martylryan.com Tenant**
|
||||
359→ - Successfully authenticated and queried tenant
|
||||
360→ - Pulled users, sign-in logs, OAuth grants, service principals
|
||||
361→ - Found suspicious sign-in failures from VPN IP (195.210.125.x - GSL Networks)
|
||||
362→ - User had already remediated account
|
||||
363→
|
||||
364→3. **Checked CIPP-SAM Permissions**
|
||||
365→ - CIPP-SAM has 54 Graph permissions
|
||||
366→ - Missing 4 for full remediation: AppRoleAssignment.ReadWrite.All, DelegatedPermissionGrant.ReadWrite.All, Mail.ReadWrite, SecurityEvents.ReadWrite.All
|
||||
367→ - User to add missing permissions via Entra portal
|
||||
368→
|
||||
369→4. **Fixed SPF Records**
|
||||
370→ - **acepickupparts.com**: Added IX IP (72.194.62.5) to SPF
|
||||
371→ - Before: `v=spf1 include:spf.us.emailservice.io -all`
|
||||
372→ - After: `v=spf1 ip4:72.194.62.5 include:spf.us.emailservice.io -all`
|
||||
373→ - **devconllc.com**: Added IX IP (72.194.62.5) to SPF
|
||||
374→ - Before: `v=spf1 +a +mx +ip4:162.248.93.233 +ip4:72.194.62.7 +include:mail.acghosting.com +include:spf.us.emailservice.io -all`
|
||||
375→ - After: `v=spf1 +a +mx +ip4:162.248.93.233 +ip4:72.194.62.7 +ip4:72.194.62.5 +include:mail.acghosting.com +include:spf.us.emailservice.io -all`
|
||||
376→
|
||||
377→5. **Checked woodenbucketcreative.com Email Records**
|
||||
378→ - Domain on Wix DNS, MX points to Google Workspace
|
||||
379→ - User fixed missing SPF and DMARC via Wix dashboard
|
||||
380→
|
||||
381→6. **Identified Glue Record Issue**
|
||||
382→ - ns1/ns2/ns3.acghosting.com had no glue records at GoDaddy
|
||||
383→ - All three were resolving to same IP (52.52.94.202 - DNS cluster)
|
||||
384→ - User added glue records at GoDaddy:
|
||||
385→ - ns1 → 162.248.93.233 (WEBSVR)
|
||||
386→ - ns2 → 72.194.62.5 (IX)
|
||||
387→ - ns3 → 52.52.94.202 (DNS Cluster)
|
||||
388→ - Propagation in progress
|
||||
389→
|
||||
390→7. **Started GuruConnect Native Viewer**
|
||||
391→ - Created viewer crate in guru-connect workspace
|
||||
392→ - Implemented WebSocket client, window rendering, low-level keyboard hooks
|
||||
393→ - Purpose: Full keyboard capture including Win key, Alt+Tab, etc.
|
||||
394→ - Files created: viewer/Cargo.toml, viewer/src/main.rs, proto.rs, transport.rs, render.rs, input.rs
|
||||
395→
|
||||
396→---
|
||||
397→
|
||||
398→### Credentials
|
||||
399→
|
||||
400→#### Claude-MSP-Access (Multi-Tenant Graph API)
|
||||
401→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
402→- **App ID (Client ID):** fabb3421-8b34-484b-bc17-e46de9703418
|
||||
403→- **Client Secret:** ~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO
|
||||
404→- **Secret Expires:** 2026-12 (24 months)
|
||||
405→- **Sign-in Audience:** Multi-tenant (any Entra ID org)
|
||||
406→- **Admin Consent URL:** https://login.microsoftonline.com/common/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
|
||||
407→
|
||||
408→#### AZ Computer Guru M365
|
||||
409→- **Admin User:** mike@azcomputerguru.com
|
||||
410→- **Password:** Window123!@#
|
||||
411→- **Note:** MFA enabled, ROPC flow blocked
|
||||
412→
|
||||
413→#### Marty Ryan Tenant (Tested)
|
||||
414→- **Tenant ID:** 48581923-2153-48b9-82b3-6a3587813041
|
||||
415→- **Domain:** martylryan.com
|
||||
416→- **Admin:** admin@martylryan.onmicrosoft.com
|
||||
417→- **Status:** Claude-MSP-Access consented, Graph API working
|
||||
418→
|
||||
419→---
|
||||
420→
|
||||
421→### Infrastructure
|
||||
422→
|
||||
423→#### Server IPs Verified
|
||||
424→| Server | IP |
|
||||
425→|--------|-----|
|
||||
426→| websvr.acghosting.com | 162.248.93.233 |
|
||||
427→| ix.azcomputerguru.com | 72.194.62.5 |
|
||||
428→| ns1.acghosting.com | Currently 52.52.94.202 (glue updating to 162.248.93.233) |
|
||||
429→| ns2.acghosting.com | Currently 52.52.94.202 (glue updating to 72.194.62.5) |
|
||||
430→| ns3.acghosting.com | 52.52.94.202 (DNS Cluster) |
|
||||
431→
|
||||
432→#### Glue Records at GoDaddy (for acghosting.com)
|
||||
433→| Hostname | IP | Server |
|
||||
434→|----------|-----|--------|
|
||||
435→| ns1 | 162.248.93.233 | WEBSVR |
|
||||
436→| ns2 | 72.194.62.5 | IX |
|
||||
437→| ns3 | 52.52.94.202 | DNS Cluster |
|
||||
438→
|
||||
439→---
|
||||
440→
|
||||
441→### Commands Reference
|
||||
442→
|
||||
443→#### Claude-MSP-Access Token & Query
|
||||
444→```python
|
||||
445→import requests
|
||||
446→
|
||||
447→tenant_id = "CUSTOMER_TENANT_ID" # After admin consent
|
||||
448→client_id = "fabb3421-8b34-484b-bc17-e46de9703418"
|
||||
449→client_secret = "~QJ8Q~NyQSs4OcGqHZyPrA2CVnq9KBfKiimntbMO"
|
||||
450→
|
||||
451→# Get token
|
||||
452→token_resp = requests.post(
|
||||
453→ f"https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token",
|
||||
454→ data={
|
||||
455→ "client_id": client_id,
|
||||
456→ "client_secret": client_secret,
|
||||
457→ "scope": "https://graph.microsoft.com/.default",
|
||||
458→ "grant_type": "client_credentials"
|
||||
459→ }
|
||||
460→)
|
||||
461→access_token = token_resp.json()["access_token"]
|
||||
462→
|
||||
463→# Query Graph API
|
||||
464→headers = {"Authorization": f"Bearer {access_token}"}
|
||||
465→users = requests.get("https://graph.microsoft.com/v1.0/users", headers=headers)
|
||||
466→signins = requests.get("https://graph.microsoft.com/v1.0/auditLogs/signIns?$top=10", headers=headers)
|
||||
467→```
|
||||
468→
|
||||
469→#### WHM API for DNS Updates
|
||||
470→```bash
|
||||
471→# Edit SPF record
|
||||
472→curl -s "https://websvr.acghosting.com:2087/json-api/editzonerecord?domain=DOMAIN&line=LINE&type=TXT&name=DOMAIN.&ttl=14400&txtdata=ENCODED_SPF" \
|
||||
473→ -H "Authorization: whm root:8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O"
|
||||
474→```
|
||||
475→
|
||||
476→---
|
||||
477→
|
||||
478→### Files Created/Modified
|
||||
479→
|
||||
480→#### GuruConnect Viewer (new crate)
|
||||
481→- `viewer/Cargo.toml` - Dependencies for native viewer
|
||||
482→- `viewer/build.rs` - Protobuf compilation
|
||||
483→- `viewer/src/main.rs` - Entry point, CLI args, main loop
|
||||
484→- `viewer/src/proto.rs` - Protobuf module
|
||||
485→- `viewer/src/transport.rs` - WebSocket client
|
||||
486→- `viewer/src/render.rs` - Window and frame rendering with softbuffer
|
||||
487→- `viewer/src/input.rs` - Low-level keyboard hooks (Win key capture)
|
||||
488→
|
||||
489→#### Workspace Updated
|
||||
490→- `Cargo.toml` - Added viewer to workspace members
|
||||
491→
|
||||
492→---
|
||||
493→
|
||||
494→### Pending Tasks
|
||||
495→
|
||||
496→#### GuruConnect Native Viewer
|
||||
497→- [ ] Build and test viewer on Windows
|
||||
498→- [ ] Test low-level keyboard hook (Win key, Alt+Tab capture)
|
||||
499→- [ ] Test mouse input forwarding
|
||||
500→- [ ] Integrate Ctrl+Alt+Del special key support
|
||||
501→- [ ] Add fullscreen toggle (F11)
|
||||
502→
|
||||
503→#### CIPP Permissions
|
||||
504→- [ ] Add missing permissions to CIPP-SAM app in Entra:
|
||||
505→ - AppRoleAssignment.ReadWrite.All
|
||||
506→ - DelegatedPermissionGrant.ReadWrite.All
|
||||
507→ - Mail.ReadWrite
|
||||
508→ - SecurityEvents.ReadWrite.All
|
||||
509→- [ ] Run SAM refresh to push to all tenants
|
||||
510→
|
||||
511→#### DNS Glue Records
|
||||
512→- [ ] Verify glue record propagation for ns1/ns2/ns3.acghosting.com
|
||||
513→- [ ] Update affected domains to use all three nameservers
|
||||
514→
|
||||
515→---
|
||||
516→
|
||||
517→### Reference
|
||||
518→
|
||||
519→#### GuruConnect Viewer Modes (Planned)
|
||||
520→| Mode | Description | Use Case |
|
||||
521→|------|-------------|----------|
|
||||
522→| Web Viewer | Browser-based, quick access | Simple support, most keys work |
|
||||
523→| Native Viewer | Full keyboard capture | Full control, Win+R, Alt+Tab |
|
||||
524→| Backstage | Admin console | CMD, file transfer, registry |
|
||||
525→
|
||||
526→#### Admin Consent URL (for new M365 tenants)
|
||||
527→```
|
||||
528→https://login.microsoftonline.com/common/adminconsent?client_id=fabb3421-8b34-484b-bc17-e46de9703418&redirect_uri=https://login.microsoftonline.com/common/oauth2/nativeclient
|
||||
529→```
|
||||
530→
|
||||
531→After customer admin clicks and approves, use their tenant ID or domain with Claude-MSP-Access credentials to query Graph API.
|
||||
532→
|
||||
533→---
|
||||
534→
|
||||
535→## Update: 17:50 - GuruConnect Native Viewer Built
|
||||
536→
|
||||
537→### Work Completed
|
||||
538→
|
||||
539→8. **Set Up Windows Development Environment**
|
||||
540→ - Installed Rust on Windows (`rustup-init.exe` with stable-x86_64-pc-windows-msvc)
|
||||
541→ - Installed Visual Studio Build Tools 2022 with C++ workload
|
||||
542→ - Installed protoc (Protocol Buffers compiler) for protobuf code generation
|
||||
543→ - Location: `C:\Users\localadmin\protoc\bin\protoc.exe`
|
||||
544→
|
||||
545→9. **Built GuruConnect Native Viewer**
|
||||
546→ - Successfully compiled for Windows with full keyboard hook support
|
||||
547→ - Output: `C:\Users\localadmin\claude-projects\guru-connect\target\x86_64-pc-windows-msvc\release\guruconnect-viewer.exe` (2.8MB)
|
||||
548→ - Features:
|
||||
549→ - Low-level keyboard hooks for Win key, Alt+Tab capture
|
||||
550→ - WebSocket client for server connection
|
||||
551→ - softbuffer rendering for frame display
|
||||
552→ - Zstd decompression
|
||||
553→ - Mouse and keyboard input forwarding
|
||||
554→
|
||||
555→### Windows Build Environment
|
||||
556→
|
||||
557→```
|
||||
558→Rust: 1.92.0 (stable-x86_64-pc-windows-msvc)
|
||||
559→Visual Studio: Build Tools 2022 with C++ workload
|
||||
560→protoc: v29.3 at C:\Users\localadmin\protoc\bin\protoc.exe
|
||||
561→```
|
||||
562→
|
||||
563→### Build Command (from PowerShell)
|
||||
564→```powershell
|
||||
565→$env:PROTOC = 'C:\Users\localadmin\protoc\bin\protoc.exe'
|
||||
566→cd 'C:\Users\localadmin\claude-projects\guru-connect'
|
||||
567→cargo build -p guruconnect-viewer --release
|
||||
568→```
|
||||
569→
|
||||
570→### Viewer Files Created
|
||||
571→| File | Purpose |
|
||||
572→|------|---------|
|
||||
573→| `viewer/Cargo.toml` | Dependencies (winit, softbuffer, tokio, windows API) |
|
||||
574→| `viewer/build.rs` | Protobuf compilation |
|
||||
575→| `viewer/src/main.rs` | Entry point, CLI args, async runtime |
|
||||
576→| `viewer/src/proto.rs` | Protobuf module |
|
||||
577→| `viewer/src/transport.rs` | WebSocket client |
|
||||
578→| `viewer/src/render.rs` | Window rendering with softbuffer |
|
||||
579→| `viewer/src/input.rs` | Low-level keyboard hooks (Win key capture) |
|
||||
580→
|
||||
581→### Viewer CLI Usage
|
||||
582→```bash
|
||||
583→guruconnect-viewer.exe -i <session_id> -s wss://connect.azcomputerguru.com/ws/viewer -a <api_key>
|
||||
584→```
|
||||
585→
|
||||
586→### Next Steps for Viewer
|
||||
587→- [ ] Test with running GuruConnect server
|
||||
588→- [ ] Add fullscreen toggle (F11)
|
||||
589→- [ ] Add Ctrl+Alt+Del special key button
|
||||
590→- [ ] Test Win key and Alt+Tab capture
|
||||
591→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1 @@
|
||||
Command running in background with ID: bc4f361. Output is being written to: C:\Users\MIKESW~1\AppData\Local\Temp\claude\C--Users-MikeSwanson-Claude\tasks\bc4f361.output
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 1
|
||||
bash: line 1: cd: /tmp/guru-connect: No such file or directory
|
||||
@@ -0,0 +1 @@
|
||||
test
|
||||
@@ -0,0 +1,14 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\relay\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
219→ }
|
||||
220→ }
|
||||
221→ }
|
||||
222→
|
||||
223→ // Cleanup
|
||||
224→ input_forward.abort();
|
||||
225→ cancel_check.abort();
|
||||
226→ // Mark agent as disconnected (persistent agents stay in list as offline)
|
||||
227→ sessions_cleanup.mark_agent_disconnected(session_id).await;
|
||||
228→
|
||||
229→ // Mark support code as completed if one was used (unless cancelled)
|
||||
230→ if let Some(ref code) = support_code_cleanup {
|
||||
231→ if !support_codes_cleanup.is_cancelled(code).await {
|
||||
@@ -0,0 +1,24 @@
|
||||
The file C:\Users\MikeSwanson\Claude\shared-data\credentials.md has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
109→---
|
||||
110→
|
||||
111→## Projects - GuruConnect
|
||||
112→
|
||||
113→### Dashboard Login
|
||||
114→- **URL:** https://gc.azcomputerguru.com/login
|
||||
115→- **Username:** admin
|
||||
116→- **Password:** uwYmX6aygmJ@ZGqv
|
||||
117→- **Role:** admin
|
||||
118→- **Created:** 2025-12-29
|
||||
119→
|
||||
120→### Database (PostgreSQL on build server)
|
||||
121→- **Host:** localhost (172.16.3.30)
|
||||
122→- **Port:** 5432
|
||||
123→- **Database:** guruconnect
|
||||
124→- **User:** guruconnect
|
||||
125→- **Password:** gc_a7f82d1e4b9c3f60
|
||||
126→- **DATABASE_URL:** `postgres://guruconnect:gc_a7f82d1e4b9c3f60@localhost:5432/guruconnect`
|
||||
127→- **Created:** 2025-12-28
|
||||
128→
|
||||
129→---
|
||||
130→
|
||||
131→## Projects - GuruRMM (continued)
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 255
|
||||
ssh: Could not resolve hostname jupiter: No such host is known.
|
||||
@@ -0,0 +1,335 @@
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: /home/guru/guru-connect/server/Cargo.toml
|
||||
workspace: /home/guru/guru-connect/Cargo.toml
|
||||
Compiling guruconnect-server v0.1.0 (/home/guru/guru-connect/server)
|
||||
warning: unused import: `JwtConfig`
|
||||
--> server/src/api/auth.rs:11:41
|
||||
|
|
||||
11 | verify_password, AuthenticatedUser, JwtConfig,
|
||||
| ^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(unused_imports)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: unused import: `Query`
|
||||
--> server/src/api/mod.rs:7:28
|
||||
|
|
||||
7 | extract::{Path, State, Query},
|
||||
| ^^^^^
|
||||
|
||||
warning: unused import: `machines::*`
|
||||
--> server/src/db/mod.rs:17:9
|
||||
|
|
||||
17 | pub use machines::*;
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: unused import: `sessions::*`
|
||||
--> server/src/db/mod.rs:18:9
|
||||
|
|
||||
18 | pub use sessions::*;
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: unused import: `events::*`
|
||||
--> server/src/db/mod.rs:19:9
|
||||
|
|
||||
19 | pub use events::*;
|
||||
| ^^^^^^^^^
|
||||
|
||||
warning: unused import: `support_codes::*`
|
||||
--> server/src/db/mod.rs:20:9
|
||||
|
|
||||
20 | pub use support_codes::*;
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: unused variable: `machine_id`
|
||||
--> server/src/relay/mod.rs:118:9
|
||||
|
|
||||
118 | let machine_id = if let Some(ref db) = db {
|
||||
| ^^^^^^^^^^ help: if this is intentional, prefix it with an underscore: `_machine_id`
|
||||
|
|
||||
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: struct `ValidateParams` is never constructed
|
||||
--> server/src/main.rs:270:8
|
||||
|
|
||||
270 | struct ValidateParams {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: fields `listen_addr`, `jwt_secret`, and `debug` are never read
|
||||
--> server/src/config.rs:10:9
|
||||
|
|
||||
8 | pub struct Config {
|
||||
| ------ fields in this struct
|
||||
9 | /// Address to listen on (e.g., "0.0.0.0:8080")
|
||||
10 | pub listen_addr: String,
|
||||
| ^^^^^^^^^^^
|
||||
...
|
||||
19 | pub jwt_secret: Option<String>,
|
||||
| ^^^^^^^^^^
|
||||
...
|
||||
22 | pub debug: bool,
|
||||
| ^^^^^
|
||||
|
|
||||
= note: `Config` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
||||
|
||||
warning: constant `HEARTBEAT_TIMEOUT_SECS` is never used
|
||||
--> server/src/session/mod.rs:30:7
|
||||
|
|
||||
30 | const HEARTBEAT_TIMEOUT_SECS: u64 = 90;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `input_rx` is never read
|
||||
--> server/src/session/mod.rs:67:5
|
||||
|
|
||||
61 | struct SessionData {
|
||||
| ----------- field in this struct
|
||||
...
|
||||
67 | input_rx: Option<InputReceiver>,
|
||||
| ^^^^^^^^
|
||||
|
||||
warning: methods `is_session_timed_out` and `get_timed_out_sessions` are never used
|
||||
--> server/src/session/mod.rs:195:18
|
||||
|
|
||||
81 | impl SessionManager {
|
||||
| ------------------- methods in this implementation
|
||||
...
|
||||
195 | pub async fn is_session_timed_out(&self, session_id: SessionId) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
205 | pub async fn get_timed_out_sessions(&self) -> Vec<SessionId> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `permissions` is never read
|
||||
--> server/src/auth/mod.rs:24:9
|
||||
|
|
||||
20 | pub struct AuthenticatedUser {
|
||||
| ----------------- field in this struct
|
||||
...
|
||||
24 | pub permissions: Vec<String>,
|
||||
| ^^^^^^^^^^^
|
||||
|
|
||||
= note: `AuthenticatedUser` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
||||
|
||||
warning: method `has_permission` is never used
|
||||
--> server/src/auth/mod.rs:29:12
|
||||
|
|
||||
27 | impl AuthenticatedUser {
|
||||
| ---------------------- method in this implementation
|
||||
28 | /// Check if user has a specific permission
|
||||
29 | pub fn has_permission(&self, permission: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `AuthenticatedAgent` is never constructed
|
||||
--> server/src/auth/mod.rs:55:12
|
||||
|
|
||||
55 | pub struct AuthenticatedAgent {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `AuthState` is never constructed
|
||||
--> server/src/auth/mod.rs:62:12
|
||||
|
|
||||
62 | pub struct AuthState {
|
||||
| ^^^^^^^^^
|
||||
|
||||
warning: associated function `new` is never used
|
||||
--> server/src/auth/mod.rs:67:12
|
||||
|
|
||||
66 | impl AuthState {
|
||||
| -------------- associated function in this implementation
|
||||
67 | pub fn new(jwt_secret: String, expiry_hours: i64) -> Self {
|
||||
| ^^^
|
||||
|
||||
warning: struct `OptionalUser` is never constructed
|
||||
--> server/src/auth/mod.rs:112:12
|
||||
|
|
||||
112 | pub struct OptionalUser(pub Option<AuthenticatedUser>);
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
warning: function `validate_agent_key` is never used
|
||||
--> server/src/auth/mod.rs:151:8
|
||||
|
|
||||
151 | pub fn validate_agent_key(_api_key: &str) -> Option<AuthenticatedAgent> {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `default_jwt_secret` is never used
|
||||
--> server/src/auth/jwt.rs:104:8
|
||||
|
|
||||
104 | pub fn default_jwt_secret() -> String {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `list_sessions` is never used
|
||||
--> server/src/api/mod.rs:75:14
|
||||
|
|
||||
75 | pub async fn list_sessions(
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_session` is never used
|
||||
--> server/src/api/mod.rs:83:14
|
||||
|
|
||||
83 | pub async fn get_session(
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: function `update_machine_status` is never used
|
||||
--> server/src/db/machines.rs:51:14
|
||||
|
|
||||
51 | pub async fn update_machine_status(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_session` is never used
|
||||
--> server/src/db/sessions.rs:67:14
|
||||
|
|
||||
67 | pub async fn get_session(pool: &PgPool, session_id: Uuid) -> Result<Option<DbSession>, sqlx::Error> {
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: function `get_active_sessions_for_machine` is never used
|
||||
--> server/src/db/sessions.rs:75:14
|
||||
|
|
||||
75 | pub async fn get_active_sessions_for_machine(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_recent_sessions` is never used
|
||||
--> server/src/db/sessions.rs:88:14
|
||||
|
|
||||
88 | pub async fn get_recent_sessions(
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: associated constants `SESSION_TIMEOUT`, `STREAMING_STARTED`, and `STREAMING_STOPPED` are never used
|
||||
--> server/src/db/events.rs:29:15
|
||||
|
|
||||
26 | impl EventTypes {
|
||||
| --------------- associated constants in this implementation
|
||||
...
|
||||
29 | pub const SESSION_TIMEOUT: &'static str = "session_timeout";
|
||||
| ^^^^^^^^^^^^^^^
|
||||
...
|
||||
32 | pub const STREAMING_STARTED: &'static str = "streaming_started";
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
33 | pub const STREAMING_STOPPED: &'static str = "streaming_stopped";
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_session_events` is never used
|
||||
--> server/src/db/events.rs:69:14
|
||||
|
|
||||
69 | pub async fn get_session_events(
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_recent_events` is never used
|
||||
--> server/src/db/events.rs:82:14
|
||||
|
|
||||
82 | pub async fn get_recent_events(
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_events_by_type` is never used
|
||||
--> server/src/db/events.rs:95:14
|
||||
|
|
||||
95 | pub async fn get_events_by_type(
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `DbSupportCode` is never constructed
|
||||
--> server/src/db/support_codes.rs:10:12
|
||||
|
|
||||
10 | pub struct DbSupportCode {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `create_support_code` is never used
|
||||
--> server/src/db/support_codes.rs:24:14
|
||||
|
|
||||
24 | pub async fn create_support_code(
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_support_code` is never used
|
||||
--> server/src/db/support_codes.rs:43:14
|
||||
|
|
||||
43 | pub async fn get_support_code(pool: &PgPool, code: &str) -> Result<Option<DbSupportCode>, sqlx::Error> {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `mark_code_cancelled` is never used
|
||||
--> server/src/db/support_codes.rs:90:14
|
||||
|
|
||||
90 | pub async fn mark_code_cancelled(pool: &PgPool, code: &str) -> Result<(), sqlx::Error> {
|
||||
| ^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_active_support_codes` is never used
|
||||
--> server/src/db/support_codes.rs:99:14
|
||||
|
|
||||
99 | pub async fn get_active_support_codes(pool: &PgPool) -> Result<Vec<DbSupportCode>, sqlx::Error> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `is_code_valid` is never used
|
||||
--> server/src/db/support_codes.rs:108:14
|
||||
|
|
||||
108 | pub async fn is_code_valid(pool: &PgPool, code: &str) -> Result<bool, sqlx::Error> {
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `is_code_cancelled` is never used
|
||||
--> server/src/db/support_codes.rs:119:14
|
||||
|
|
||||
119 | pub async fn is_code_cancelled(pool: &PgPool, code: &str) -> Result<bool, sqlx::Error> {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `link_session_to_code` is never used
|
||||
--> server/src/db/support_codes.rs:130:14
|
||||
|
|
||||
130 | pub async fn link_session_to_code(
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `updated_at` is never read
|
||||
--> server/src/db/users.rs:18:9
|
||||
|
|
||||
10 | pub struct User {
|
||||
| ---- field in this struct
|
||||
...
|
||||
18 | pub updated_at: DateTime<Utc>,
|
||||
| ^^^^^^^^^^
|
||||
|
|
||||
= note: `User` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
||||
|
||||
warning: struct `UserInfo` is never constructed
|
||||
--> server/src/db/users.rs:24:12
|
||||
|
|
||||
24 | pub struct UserInfo {
|
||||
| ^^^^^^^^
|
||||
|
||||
warning: function `get_user_client_access` is never used
|
||||
--> server/src/db/users.rs:210:14
|
||||
|
|
||||
210 | pub async fn get_user_client_access(pool: &PgPool, user_id: Uuid) -> Result<Vec<Uuid>> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `user_has_client_access` is never used
|
||||
--> server/src/db/users.rs:246:14
|
||||
|
|
||||
246 | pub async fn user_has_client_access(
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `technician_id` is never read
|
||||
--> server/src/support_codes.rs:39:9
|
||||
|
|
||||
38 | pub struct CreateCodeRequest {
|
||||
| ----------------- field in this struct
|
||||
39 | pub technician_id: Option<String>,
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `CreateCodeRequest` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
||||
|
||||
warning: methods `get_code`, `is_valid_for_connection`, `list_codes`, and `get_by_session` are never used
|
||||
--> server/src/support_codes.rs:166:18
|
||||
|
|
||||
60 | impl SupportCodeManager {
|
||||
| ----------------------- methods in this implementation
|
||||
...
|
||||
166 | pub async fn get_code(&self, code: &str) -> Option<SupportCode> {
|
||||
| ^^^^^^^^
|
||||
...
|
||||
198 | pub async fn is_valid_for_connection(&self, code: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
204 | pub async fn list_codes(&self) -> Vec<SupportCode> {
|
||||
| ^^^^^^^^^^
|
||||
...
|
||||
219 | pub async fn get_by_session(&self, session_id: Uuid) -> Option<SupportCode> {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: `guruconnect-server` (bin "guruconnect-server") generated 44 warnings (run `cargo fix --bin "guruconnect-server" -p guruconnect-server` to apply 7 suggestions)
|
||||
Finished `release` profile [optimized] target(s) in 15.62s
|
||||
@@ -0,0 +1,487 @@
|
||||
1→//! GuruConnect Server - WebSocket Relay Server
|
||||
2→//!
|
||||
3→//! Handles connections from both agents and dashboard viewers,
|
||||
4→//! relaying video frames and input events between them.
|
||||
5→
|
||||
6→mod config;
|
||||
7→mod relay;
|
||||
8→mod session;
|
||||
9→mod auth;
|
||||
10→mod api;
|
||||
11→mod db;
|
||||
12→mod support_codes;
|
||||
13→
|
||||
14→pub mod proto {
|
||||
15→ include!(concat!(env!("OUT_DIR"), "/guruconnect.rs"));
|
||||
16→}
|
||||
17→
|
||||
18→use anyhow::Result;
|
||||
19→use axum::{
|
||||
20→ Router,
|
||||
21→<<<<<<< HEAD
|
||||
22→ routing::{get, post, delete},
|
||||
23→ extract::{Path, State, Json, Query},
|
||||
24→=======
|
||||
25→ routing::{get, post, put, delete},
|
||||
26→ extract::{Path, State, Json, Request},
|
||||
27→>>>>>>> b861cb1 (Add user management system with JWT authentication)
|
||||
28→ response::{Html, IntoResponse},
|
||||
29→ http::StatusCode,
|
||||
30→ middleware::{self, Next},
|
||||
31→};
|
||||
32→use std::net::SocketAddr;
|
||||
33→use std::sync::Arc;
|
||||
34→use tower_http::cors::{Any, CorsLayer};
|
||||
35→use tower_http::trace::TraceLayer;
|
||||
36→use tower_http::services::ServeDir;
|
||||
37→use tracing::{info, Level};
|
||||
38→use tracing_subscriber::FmtSubscriber;
|
||||
39→use serde::Deserialize;
|
||||
40→
|
||||
41→use support_codes::{SupportCodeManager, CreateCodeRequest, SupportCode, CodeValidation};
|
||||
42→use auth::{JwtConfig, hash_password, generate_random_password};
|
||||
43→
|
||||
44→/// Application state
|
||||
45→#[derive(Clone)]
|
||||
46→pub struct AppState {
|
||||
47→ sessions: session::SessionManager,
|
||||
48→ support_codes: SupportCodeManager,
|
||||
49→ db: Option<db::Database>,
|
||||
50→ pub jwt_config: Arc<JwtConfig>,
|
||||
51→}
|
||||
52→
|
||||
53→/// Middleware to inject JWT config into request extensions
|
||||
54→async fn auth_layer(
|
||||
55→ State(state): State<AppState>,
|
||||
56→ mut request: Request,
|
||||
57→ next: Next,
|
||||
58→) -> impl IntoResponse {
|
||||
59→ request.extensions_mut().insert(state.jwt_config.clone());
|
||||
60→ next.run(request).await
|
||||
61→}
|
||||
62→
|
||||
63→#[tokio::main]
|
||||
64→async fn main() -> Result<()> {
|
||||
65→ // Initialize logging
|
||||
66→ let _subscriber = FmtSubscriber::builder()
|
||||
67→ .with_max_level(Level::INFO)
|
||||
68→ .with_target(true)
|
||||
69→ .init();
|
||||
70→
|
||||
71→ info!("GuruConnect Server v{}", env!("CARGO_PKG_VERSION"));
|
||||
72→
|
||||
73→ // Load configuration
|
||||
74→ let config = config::Config::load()?;
|
||||
75→
|
||||
76→ // Use port 3002 for GuruConnect
|
||||
77→ let listen_addr = std::env::var("LISTEN_ADDR").unwrap_or_else(|_| "0.0.0.0:3002".to_string());
|
||||
78→ info!("Loaded configuration, listening on {}", listen_addr);
|
||||
79→
|
||||
80→ // JWT configuration
|
||||
81→ let jwt_secret = std::env::var("JWT_SECRET").unwrap_or_else(|_| {
|
||||
82→ tracing::warn!("JWT_SECRET not set, using default (INSECURE for production!)");
|
||||
83→ "guruconnect-dev-secret-change-me-in-production".to_string()
|
||||
84→ });
|
||||
85→ let jwt_expiry_hours = std::env::var("JWT_EXPIRY_HOURS")
|
||||
86→ .ok()
|
||||
87→ .and_then(|s| s.parse().ok())
|
||||
88→ .unwrap_or(24i64);
|
||||
89→ let jwt_config = Arc::new(JwtConfig::new(jwt_secret, jwt_expiry_hours));
|
||||
90→
|
||||
91→ // Initialize database if configured
|
||||
92→ let database = if let Some(ref db_url) = config.database_url {
|
||||
93→ match db::Database::connect(db_url, config.database_max_connections).await {
|
||||
94→ Ok(db) => {
|
||||
95→ // Run migrations
|
||||
96→ if let Err(e) = db.migrate().await {
|
||||
97→ tracing::error!("Failed to run migrations: {}", e);
|
||||
98→ return Err(e);
|
||||
99→ }
|
||||
100→ Some(db)
|
||||
101→ }
|
||||
102→ Err(e) => {
|
||||
103→ tracing::warn!("Failed to connect to database: {}. Running without persistence.", e);
|
||||
104→ None
|
||||
105→ }
|
||||
106→ }
|
||||
107→ } else {
|
||||
108→ info!("No DATABASE_URL set, running without persistence");
|
||||
109→ None
|
||||
110→ };
|
||||
111→
|
||||
112→ // Create initial admin user if no users exist
|
||||
113→ if let Some(ref db) = database {
|
||||
114→ match db::count_users(db.pool()).await {
|
||||
115→ Ok(0) => {
|
||||
116→ info!("No users found, creating initial admin user...");
|
||||
117→ let password = generate_random_password(16);
|
||||
118→ let password_hash = hash_password(&password)?;
|
||||
119→
|
||||
120→ match db::create_user(db.pool(), "admin", &password_hash, None, "admin").await {
|
||||
121→ Ok(user) => {
|
||||
122→ // Set admin permissions
|
||||
123→ let perms = vec![
|
||||
124→ "view".to_string(),
|
||||
125→ "control".to_string(),
|
||||
126→ "transfer".to_string(),
|
||||
127→ "manage_users".to_string(),
|
||||
128→ "manage_clients".to_string(),
|
||||
129→ ];
|
||||
130→ let _ = db::set_user_permissions(db.pool(), user.id, &perms).await;
|
||||
131→
|
||||
132→ info!("========================================");
|
||||
133→ info!(" INITIAL ADMIN USER CREATED");
|
||||
134→ info!(" Username: admin");
|
||||
135→ info!(" Password: {}", password);
|
||||
136→ info!(" (Change this password after first login!)");
|
||||
137→ info!("========================================");
|
||||
138→ }
|
||||
139→ Err(e) => {
|
||||
140→ tracing::error!("Failed to create initial admin user: {}", e);
|
||||
141→ }
|
||||
142→ }
|
||||
143→ }
|
||||
144→ Ok(count) => {
|
||||
145→ info!("{} user(s) in database", count);
|
||||
146→ }
|
||||
147→ Err(e) => {
|
||||
148→ tracing::warn!("Could not check user count: {}", e);
|
||||
149→ }
|
||||
150→ }
|
||||
151→ }
|
||||
152→
|
||||
153→ // Create session manager
|
||||
154→ let sessions = session::SessionManager::new();
|
||||
155→
|
||||
156→ // Restore persistent machines from database
|
||||
157→ if let Some(ref db) = database {
|
||||
158→ match db::machines::get_all_machines(db.pool()).await {
|
||||
159→ Ok(machines) => {
|
||||
160→ info!("Restoring {} persistent machines from database", machines.len());
|
||||
161→ for machine in machines {
|
||||
162→ sessions.restore_offline_machine(&machine.agent_id, &machine.hostname).await;
|
||||
163→ }
|
||||
164→ }
|
||||
165→ Err(e) => {
|
||||
166→ tracing::warn!("Failed to restore machines: {}", e);
|
||||
167→ }
|
||||
168→ }
|
||||
169→ }
|
||||
170→
|
||||
171→ // Create application state
|
||||
172→ let state = AppState {
|
||||
173→ sessions,
|
||||
174→ support_codes: SupportCodeManager::new(),
|
||||
175→ db: database,
|
||||
176→ jwt_config,
|
||||
177→ };
|
||||
178→
|
||||
179→ // Build router
|
||||
180→ let app = Router::new()
|
||||
181→ // Health check (no auth required)
|
||||
182→ .route("/health", get(health))
|
||||
183→
|
||||
184→ // Auth endpoints (no auth required for login)
|
||||
185→ .route("/api/auth/login", post(api::auth::login))
|
||||
186→
|
||||
187→ // Auth endpoints (auth required)
|
||||
188→ .route("/api/auth/me", get(api::auth::get_me))
|
||||
189→ .route("/api/auth/change-password", post(api::auth::change_password))
|
||||
190→
|
||||
191→ // User management (admin only)
|
||||
192→ .route("/api/users", get(api::users::list_users))
|
||||
193→ .route("/api/users", post(api::users::create_user))
|
||||
194→ .route("/api/users/:id", get(api::users::get_user))
|
||||
195→ .route("/api/users/:id", put(api::users::update_user))
|
||||
196→ .route("/api/users/:id", delete(api::users::delete_user))
|
||||
197→ .route("/api/users/:id/permissions", put(api::users::set_permissions))
|
||||
198→ .route("/api/users/:id/clients", put(api::users::set_client_access))
|
||||
199→
|
||||
200→ // Portal API - Support codes
|
||||
201→ .route("/api/codes", post(create_code))
|
||||
202→ .route("/api/codes", get(list_codes))
|
||||
203→ .route("/api/codes/:code/validate", get(validate_code))
|
||||
204→ .route("/api/codes/:code/cancel", post(cancel_code))
|
||||
205→
|
||||
206→ // WebSocket endpoints
|
||||
207→ .route("/ws/agent", get(relay::agent_ws_handler))
|
||||
208→ .route("/ws/viewer", get(relay::viewer_ws_handler))
|
||||
209→
|
||||
210→ // REST API - Sessions
|
||||
211→ .route("/api/sessions", get(list_sessions))
|
||||
212→ .route("/api/sessions/:id", get(get_session))
|
||||
213→ .route("/api/sessions/:id", delete(disconnect_session))
|
||||
214→
|
||||
215→<<<<<<< HEAD
|
||||
216→ // REST API - Machines
|
||||
217→ .route("/api/machines", get(list_machines))
|
||||
218→ .route("/api/machines/:agent_id", get(get_machine))
|
||||
219→ .route("/api/machines/:agent_id", delete(delete_machine))
|
||||
220→ .route("/api/machines/:agent_id/history", get(get_machine_history))
|
||||
221→
|
||||
222→=======
|
||||
223→>>>>>>> b861cb1 (Add user management system with JWT authentication)
|
||||
224→ // HTML page routes (clean URLs)
|
||||
225→ .route("/login", get(serve_login))
|
||||
226→ .route("/dashboard", get(serve_dashboard))
|
||||
227→ .route("/users", get(serve_users))
|
||||
228→
|
||||
229→ // State and middleware
|
||||
230→ .with_state(state.clone())
|
||||
231→ .layer(middleware::from_fn_with_state(state, auth_layer))
|
||||
232→
|
||||
233→ // Serve static files for portal (fallback)
|
||||
234→ .fallback_service(ServeDir::new("static").append_index_html_on_directories(true))
|
||||
235→
|
||||
236→ // Middleware
|
||||
237→ .layer(TraceLayer::new_for_http())
|
||||
238→ .layer(
|
||||
239→ CorsLayer::new()
|
||||
240→ .allow_origin(Any)
|
||||
241→ .allow_methods(Any)
|
||||
242→ .allow_headers(Any),
|
||||
243→ );
|
||||
244→
|
||||
245→ // Start server
|
||||
246→ let addr: SocketAddr = listen_addr.parse()?;
|
||||
247→ let listener = tokio::net::TcpListener::bind(addr).await?;
|
||||
248→
|
||||
249→ info!("Server listening on {}", addr);
|
||||
250→
|
||||
251→ axum::serve(listener, app).await?;
|
||||
252→
|
||||
253→ Ok(())
|
||||
254→}
|
||||
255→
|
||||
256→async fn health() -> &'static str {
|
||||
257→ "OK"
|
||||
258→}
|
||||
259→
|
||||
260→// Support code API handlers
|
||||
261→
|
||||
262→async fn create_code(
|
||||
263→ State(state): State<AppState>,
|
||||
264→ Json(request): Json<CreateCodeRequest>,
|
||||
265→) -> Json<SupportCode> {
|
||||
266→ let code = state.support_codes.create_code(request).await;
|
||||
267→ info!("Created support code: {}", code.code);
|
||||
268→ Json(code)
|
||||
269→}
|
||||
270→
|
||||
271→async fn list_codes(
|
||||
272→ State(state): State<AppState>,
|
||||
273→) -> Json<Vec<SupportCode>> {
|
||||
274→ Json(state.support_codes.list_active_codes().await)
|
||||
275→}
|
||||
276→
|
||||
277→#[derive(Deserialize)]
|
||||
278→struct ValidateParams {
|
||||
279→ code: String,
|
||||
280→}
|
||||
281→
|
||||
282→async fn validate_code(
|
||||
283→ State(state): State<AppState>,
|
||||
284→ Path(code): Path<String>,
|
||||
285→) -> Json<CodeValidation> {
|
||||
286→ Json(state.support_codes.validate_code(&code).await)
|
||||
287→}
|
||||
288→
|
||||
289→async fn cancel_code(
|
||||
290→ State(state): State<AppState>,
|
||||
291→ Path(code): Path<String>,
|
||||
292→) -> impl IntoResponse {
|
||||
293→ if state.support_codes.cancel_code(&code).await {
|
||||
294→ (StatusCode::OK, "Code cancelled")
|
||||
295→ } else {
|
||||
296→ (StatusCode::BAD_REQUEST, "Cannot cancel code")
|
||||
297→ }
|
||||
298→}
|
||||
299→
|
||||
300→// Session API handlers (updated to use AppState)
|
||||
301→
|
||||
302→async fn list_sessions(
|
||||
303→ State(state): State<AppState>,
|
||||
304→) -> Json<Vec<api::SessionInfo>> {
|
||||
305→ let sessions = state.sessions.list_sessions().await;
|
||||
306→ Json(sessions.into_iter().map(api::SessionInfo::from).collect())
|
||||
307→}
|
||||
308→
|
||||
309→async fn get_session(
|
||||
310→ State(state): State<AppState>,
|
||||
311→ Path(id): Path<String>,
|
||||
312→) -> Result<Json<api::SessionInfo>, (StatusCode, &'static str)> {
|
||||
313→ let session_id = uuid::Uuid::parse_str(&id)
|
||||
314→ .map_err(|_| (StatusCode::BAD_REQUEST, "Invalid session ID"))?;
|
||||
315→
|
||||
316→ let session = state.sessions.get_session(session_id).await
|
||||
317→ .ok_or((StatusCode::NOT_FOUND, "Session not found"))?;
|
||||
318→
|
||||
319→ Ok(Json(api::SessionInfo::from(session)))
|
||||
320→}
|
||||
321→
|
||||
322→async fn disconnect_session(
|
||||
323→ State(state): State<AppState>,
|
||||
324→ Path(id): Path<String>,
|
||||
325→) -> impl IntoResponse {
|
||||
326→ let session_id = match uuid::Uuid::parse_str(&id) {
|
||||
327→ Ok(id) => id,
|
||||
328→ Err(_) => return (StatusCode::BAD_REQUEST, "Invalid session ID"),
|
||||
329→ };
|
||||
330→
|
||||
331→ if state.sessions.disconnect_session(session_id, "Disconnected by administrator").await {
|
||||
332→ info!("Session {} disconnected by admin", session_id);
|
||||
333→ (StatusCode::OK, "Session disconnected")
|
||||
334→ } else {
|
||||
335→ (StatusCode::NOT_FOUND, "Session not found")
|
||||
336→ }
|
||||
337→}
|
||||
338→
|
||||
339→// Machine API handlers
|
||||
340→
|
||||
341→async fn list_machines(
|
||||
342→ State(state): State<AppState>,
|
||||
343→) -> Result<Json<Vec<api::MachineInfo>>, (StatusCode, &'static str)> {
|
||||
344→ let db = state.db.as_ref()
|
||||
345→ .ok_or((StatusCode::SERVICE_UNAVAILABLE, "Database not available"))?;
|
||||
346→
|
||||
347→ let machines = db::machines::get_all_machines(db.pool()).await
|
||||
348→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?;
|
||||
349→
|
||||
350→ Ok(Json(machines.into_iter().map(api::MachineInfo::from).collect()))
|
||||
351→}
|
||||
352→
|
||||
353→async fn get_machine(
|
||||
354→ State(state): State<AppState>,
|
||||
355→ Path(agent_id): Path<String>,
|
||||
356→) -> Result<Json<api::MachineInfo>, (StatusCode, &'static str)> {
|
||||
357→ let db = state.db.as_ref()
|
||||
358→ .ok_or((StatusCode::SERVICE_UNAVAILABLE, "Database not available"))?;
|
||||
359→
|
||||
360→ let machine = db::machines::get_machine_by_agent_id(db.pool(), &agent_id).await
|
||||
361→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?
|
||||
362→ .ok_or((StatusCode::NOT_FOUND, "Machine not found"))?;
|
||||
363→
|
||||
364→ Ok(Json(api::MachineInfo::from(machine)))
|
||||
365→}
|
||||
366→
|
||||
367→async fn get_machine_history(
|
||||
368→ State(state): State<AppState>,
|
||||
369→ Path(agent_id): Path<String>,
|
||||
370→) -> Result<Json<api::MachineHistory>, (StatusCode, &'static str)> {
|
||||
371→ let db = state.db.as_ref()
|
||||
372→ .ok_or((StatusCode::SERVICE_UNAVAILABLE, "Database not available"))?;
|
||||
373→
|
||||
374→ // Get machine
|
||||
375→ let machine = db::machines::get_machine_by_agent_id(db.pool(), &agent_id).await
|
||||
376→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?
|
||||
377→ .ok_or((StatusCode::NOT_FOUND, "Machine not found"))?;
|
||||
378→
|
||||
379→ // Get sessions for this machine
|
||||
380→ let sessions = db::sessions::get_sessions_for_machine(db.pool(), machine.id).await
|
||||
381→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?;
|
||||
382→
|
||||
383→ // Get events for this machine
|
||||
384→ let events = db::events::get_events_for_machine(db.pool(), machine.id).await
|
||||
385→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?;
|
||||
386→
|
||||
387→ let history = api::MachineHistory {
|
||||
388→ machine: api::MachineInfo::from(machine),
|
||||
389→ sessions: sessions.into_iter().map(api::SessionRecord::from).collect(),
|
||||
390→ events: events.into_iter().map(api::EventRecord::from).collect(),
|
||||
391→ exported_at: chrono::Utc::now().to_rfc3339(),
|
||||
392→ };
|
||||
393→
|
||||
394→ Ok(Json(history))
|
||||
395→}
|
||||
396→
|
||||
397→async fn delete_machine(
|
||||
398→ State(state): State<AppState>,
|
||||
399→ Path(agent_id): Path<String>,
|
||||
400→ Query(params): Query<api::DeleteMachineParams>,
|
||||
401→) -> Result<Json<api::DeleteMachineResponse>, (StatusCode, &'static str)> {
|
||||
402→ let db = state.db.as_ref()
|
||||
403→ .ok_or((StatusCode::SERVICE_UNAVAILABLE, "Database not available"))?;
|
||||
404→
|
||||
405→ // Get machine first
|
||||
406→ let machine = db::machines::get_machine_by_agent_id(db.pool(), &agent_id).await
|
||||
407→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?
|
||||
408→ .ok_or((StatusCode::NOT_FOUND, "Machine not found"))?;
|
||||
409→
|
||||
410→ // Export history if requested
|
||||
411→ let history = if params.export {
|
||||
412→ let sessions = db::sessions::get_sessions_for_machine(db.pool(), machine.id).await
|
||||
413→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?;
|
||||
414→ let events = db::events::get_events_for_machine(db.pool(), machine.id).await
|
||||
415→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Database error"))?;
|
||||
416→
|
||||
417→ Some(api::MachineHistory {
|
||||
418→ machine: api::MachineInfo::from(machine.clone()),
|
||||
419→ sessions: sessions.into_iter().map(api::SessionRecord::from).collect(),
|
||||
420→ events: events.into_iter().map(api::EventRecord::from).collect(),
|
||||
421→ exported_at: chrono::Utc::now().to_rfc3339(),
|
||||
422→ })
|
||||
423→ } else {
|
||||
424→ None
|
||||
425→ };
|
||||
426→
|
||||
427→ // Send uninstall command if requested and agent is online
|
||||
428→ let mut uninstall_sent = false;
|
||||
429→ if params.uninstall {
|
||||
430→ // Find session for this agent
|
||||
431→ if let Some(session) = state.sessions.get_session_by_agent(&agent_id).await {
|
||||
432→ if session.is_online {
|
||||
433→ uninstall_sent = state.sessions.send_admin_command(
|
||||
434→ session.id,
|
||||
435→ proto::AdminCommandType::AdminUninstall,
|
||||
436→ "Deleted by administrator",
|
||||
437→ ).await;
|
||||
438→ if uninstall_sent {
|
||||
439→ info!("Sent uninstall command to agent {}", agent_id);
|
||||
440→ }
|
||||
441→ }
|
||||
442→ }
|
||||
443→ }
|
||||
444→
|
||||
445→ // Remove from session manager
|
||||
446→ state.sessions.remove_agent(&agent_id).await;
|
||||
447→
|
||||
448→ // Delete from database (cascades to sessions and events)
|
||||
449→ db::machines::delete_machine(db.pool(), &agent_id).await
|
||||
450→ .map_err(|_| (StatusCode::INTERNAL_SERVER_ERROR, "Failed to delete machine"))?;
|
||||
451→
|
||||
452→ info!("Deleted machine {} (uninstall_sent: {})", agent_id, uninstall_sent);
|
||||
453→
|
||||
454→ Ok(Json(api::DeleteMachineResponse {
|
||||
455→ success: true,
|
||||
456→ message: format!("Machine {} deleted", machine.hostname),
|
||||
457→ uninstall_sent,
|
||||
458→ history,
|
||||
459→ }))
|
||||
460→}
|
||||
461→
|
||||
462→// Static page handlers
|
||||
463→async fn serve_login() -> impl IntoResponse {
|
||||
464→ match tokio::fs::read_to_string("static/login.html").await {
|
||||
465→ Ok(content) => Html(content).into_response(),
|
||||
466→ Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
|
||||
467→ }
|
||||
468→}
|
||||
469→
|
||||
470→async fn serve_dashboard() -> impl IntoResponse {
|
||||
471→ match tokio::fs::read_to_string("static/dashboard.html").await {
|
||||
472→ Ok(content) => Html(content).into_response(),
|
||||
473→ Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
|
||||
474→ }
|
||||
475→}
|
||||
476→
|
||||
477→async fn serve_users() -> impl IntoResponse {
|
||||
478→ match tokio::fs::read_to_string("static/users.html").await {
|
||||
479→ Ok(content) => Html(content).into_response(),
|
||||
480→ Err(_) => (StatusCode::NOT_FOUND, "Page not found").into_response(),
|
||||
481→ }
|
||||
482→}
|
||||
483→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,3 @@
|
||||
Exit code 1
|
||||
Access denied
|
||||
FATAL ERROR: Configured password was not accepted
|
||||
@@ -0,0 +1,556 @@
|
||||
1→# Credentials & Authorization Reference
|
||||
2→**Last Updated:** 2025-12-16
|
||||
3→**Purpose:** Centralized credentials for Claude Code context recovery across all machines
|
||||
4→
|
||||
5→---
|
||||
6→
|
||||
7→## Infrastructure - SSH Access
|
||||
8→
|
||||
9→### Jupiter (Unraid Primary)
|
||||
10→- **Host:** 172.16.3.20
|
||||
11→- **User:** root
|
||||
12→- **Port:** 22
|
||||
13→- **Password:** Th1nk3r^99##
|
||||
14→- **WebUI Password:** Th1nk3r^99##
|
||||
15→- **Role:** Primary container host (Gitea, NPM, GuruRMM, media)
|
||||
16→- **iDRAC IP:** 172.16.1.73 (DHCP)
|
||||
17→- **iDRAC User:** root
|
||||
18→- **iDRAC Password:** Window123!@#-idrac
|
||||
19→- **iDRAC SSH:** Enabled (port 22)
|
||||
20→- **IPMI Key:** All zeros
|
||||
21→
|
||||
22→### Saturn (Unraid Secondary)
|
||||
23→- **Host:** 172.16.3.21
|
||||
24→- **User:** root
|
||||
25→- **Port:** 22
|
||||
26→- **Password:** r3tr0gradE99
|
||||
27→- **Role:** Migration source, being consolidated to Jupiter
|
||||
28→
|
||||
29→### pfSense (Firewall)
|
||||
30→- **Host:** 172.16.0.1
|
||||
31→- **User:** admin
|
||||
32→- **Port:** 2248
|
||||
33→- **Password:** r3tr0gradE99!!
|
||||
34→- **Role:** Firewall, Tailscale gateway
|
||||
35→- **Tailscale IP:** 100.79.69.82 (pfsense-1)
|
||||
36→
|
||||
37→### OwnCloud VM (on Jupiter)
|
||||
38→- **Host:** 172.16.3.22
|
||||
39→- **Hostname:** cloud.acghosting.com
|
||||
40→- **User:** root
|
||||
41→- **Port:** 22
|
||||
42→- **Password:** Paper123!@#-unifi!
|
||||
43→- **OS:** Rocky Linux 9.6
|
||||
44→- **Role:** OwnCloud file sync server
|
||||
45→- **Services:** Apache, MariaDB, PHP-FPM, Redis, Datto RMM agents
|
||||
46→- **Storage:** SMB mount from Jupiter (/mnt/user/OwnCloud)
|
||||
47→- **Note:** Jupiter has SSH key auth configured
|
||||
48→
|
||||
49→### GuruRMM Build Server
|
||||
50→- **Host:** 172.16.3.30
|
||||
51→- **Hostname:** gururmm
|
||||
52→- **User:** guru
|
||||
53→- **Port:** 22
|
||||
54→- **Password:** Gptf*77ttb123!@#-rmm
|
||||
55→- **Sudo Password:** Gptf*77ttb123!@#-rmm
|
||||
56→- **OS:** Ubuntu 22.04
|
||||
57→- **Role:** GuruRMM dedicated server (API, DB, Dashboard, Downloads)
|
||||
58→- **Services:** nginx, PostgreSQL, gururmm-server, gururmm-agent
|
||||
59→- **Note:** WSL has SSH key auth configured; sudo requires heredoc for password with special chars
|
||||
60→
|
||||
61→---
|
||||
62→
|
||||
63→## Services - Web Applications
|
||||
64→
|
||||
65→### Gitea (Git Server)
|
||||
66→- **URL:** https://git.azcomputerguru.com/
|
||||
67→- **Internal:** http://172.16.3.20:3000
|
||||
68→- **SSH:** ssh://git@172.16.3.20:2222
|
||||
69→- **User:** mike@azcomputerguru.com
|
||||
70→- **Password:** Window123!@#-git
|
||||
71→- **API Token:** 9b1da4b79a38ef782268341d25a4b6880572063f
|
||||
72→
|
||||
73→### NPM (Nginx Proxy Manager)
|
||||
74→- **Admin URL:** http://172.16.3.20:7818
|
||||
75→- **HTTP Port:** 1880
|
||||
76→- **HTTPS Port:** 18443
|
||||
77→- **User:** mike@azcomputerguru.com
|
||||
78→- **Password:** Paper123!@#-unifi
|
||||
79→
|
||||
80→### Cloudflare
|
||||
81→- **API Token (Full DNS):** DRRGkHS33pxAUjQfRDzDeVPtt6wwUU6FwtXqOzNj
|
||||
82→- **API Token (Legacy/Limited):** U1UTbBOWA4a69eWEBiqIbYh0etCGzrpTU4XaKp7w
|
||||
83→- **Permissions:** Zone:Read, Zone:Edit, DNS:Read, DNS:Edit
|
||||
84→- **Used for:** DNS management, WHM plugin, cf-dns CLI
|
||||
85→- **Domain:** azcomputerguru.com
|
||||
86→- **Notes:** New full-access token added 2025-12-19
|
||||
87→
|
||||
88→---
|
||||
89→
|
||||
90→## Projects - GuruRMM
|
||||
91→
|
||||
92→### Dashboard/API Login
|
||||
93→- **Email:** admin@azcomputerguru.com
|
||||
94→- **Password:** GuruRMM2025
|
||||
95→- **Role:** admin
|
||||
96→
|
||||
97→### Database (PostgreSQL)
|
||||
98→- **Host:** gururmm-db container (172.16.3.20)
|
||||
99→- **Database:** gururmm
|
||||
100→- **User:** gururmm
|
||||
101→- **Password:** 43617ebf7eb242e814ca9988cc4df5ad
|
||||
102→
|
||||
103→### API Server
|
||||
104→- **External URL:** https://rmm-api.azcomputerguru.com
|
||||
105→- **Internal URL:** http://172.16.3.20:3001
|
||||
106→- **JWT Secret:** ZNzGxghru2XUdBVlaf2G2L1YUBVcl5xH0lr/Gpf/QmE=
|
||||
107→
|
||||
108→### Microsoft Entra ID (SSO)
|
||||
109→- **App Name:** GuruRMM Dashboard
|
||||
110→- **App ID (Client ID):** 18a15f5d-7ab8-46f4-8566-d7b5436b84b6
|
||||
111→- **Object ID:** 34c80aa8-385a-4bea-af85-f8bf67decc8f
|
||||
112→- **Client Secret:** gOz8Q~J.oz7KnUIEpzmHOyJ6GEzYNecGRl-Pbc9w
|
||||
113→- **Secret Expires:** 2026-12-21
|
||||
114→- **Sign-in Audience:** Multi-tenant (any Azure AD org)
|
||||
115→- **Redirect URIs:** https://rmm.azcomputerguru.com/auth/callback, http://localhost:5173/auth/callback
|
||||
116→- **API Permissions:** openid, email, profile
|
||||
117→- **Notes:** Created 2025-12-21 for GuruRMM SSO
|
||||
118→
|
||||
119→### CI/CD (Build Automation)
|
||||
120→- **Webhook URL:** http://172.16.3.30/webhook/build
|
||||
121→- **Webhook Secret:** gururmm-build-secret
|
||||
122→- **Build Script:** /opt/gururmm/build-agents.sh
|
||||
123→- **Build Log:** /var/log/gururmm-build.log
|
||||
124→- **Gitea Webhook ID:** 1
|
||||
125→- **Trigger:** Push to main branch
|
||||
126→- **Builds:** Linux (x86_64) and Windows (x86_64) agents
|
||||
127→- **Deploy Path:** /var/www/gururmm/downloads/
|
||||
128→
|
||||
129→### Build Server SSH Key (for Gitea)
|
||||
130→- **Key Name:** gururmm-build-server
|
||||
131→- **Public Key:**
|
||||
132→```
|
||||
133→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIKSqf2/phEXUK8vd5GhMIDTEGSk0LvYk92sRdNiRrjKi guru@gururmm-build
|
||||
134→```
|
||||
135→- **Added to:** Gitea (azcomputerguru account)
|
||||
136→
|
||||
137→### Clients & Sites
|
||||
138→#### Glaztech Industries (GLAZ)
|
||||
139→- **Client ID:** d857708c-5713-4ee5-a314-679f86d2f9f9
|
||||
140→- **Site:** SLC - Salt Lake City
|
||||
141→- **Site ID:** 290bd2ea-4af5-49c6-8863-c6d58c5a55de
|
||||
142→- **Site Code:** DARK-GROVE-7839
|
||||
143→- **API Key:** grmm_Qw64eawPBjnMdwN5UmDGWoPlqwvjM7lI
|
||||
144→- **Created:** 2025-12-18
|
||||
145→
|
||||
146→---
|
||||
147→
|
||||
148→## Client Sites - WHM/cPanel
|
||||
149→
|
||||
150→### IX Server (ix.azcomputerguru.com)
|
||||
151→- **SSH Host:** ix.azcomputerguru.com
|
||||
152→- **Internal IP:** 172.16.3.10 (VPN required)
|
||||
153→- **SSH User:** root
|
||||
154→- **SSH Password:** Gptf*77ttb!@#!@#
|
||||
155→- **SSH Key:** guru@wsl key added to authorized_keys
|
||||
156→- **Role:** cPanel/WHM server hosting client sites
|
||||
157→
|
||||
158→### WebSvr (websvr.acghosting.com)
|
||||
159→- **Host:** websvr.acghosting.com
|
||||
160→- **SSH User:** root
|
||||
161→- **SSH Password:** r3tr0gradE99#
|
||||
162→- **API Token:** 8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O
|
||||
163→- **Access Level:** Full access
|
||||
164→- **Role:** Legacy cPanel/WHM server (migration source to IX)
|
||||
165→
|
||||
166→### data.grabbanddurando.com
|
||||
167→- **Server:** IX (ix.azcomputerguru.com)
|
||||
168→- **cPanel Account:** grabblaw
|
||||
169→- **Site Path:** /home/grabblaw/public_html/data_grabbanddurando
|
||||
170→- **Site Admin User:** admin
|
||||
171→- **Site Admin Password:** GND-Paper123!@#-datasite
|
||||
172→- **Database:** grabblaw_gdapp_data
|
||||
173→- **DB User:** grabblaw_gddata
|
||||
174→- **DB Password:** GrabbData2025
|
||||
175→- **Config File:** /home/grabblaw/public_html/data_grabbanddurando/connection.php
|
||||
176→- **Backups:** /home/grabblaw/public_html/data_grabbanddurando/backups_mariadb_fix/
|
||||
177→
|
||||
178→### GoDaddy VPS (Legacy)
|
||||
179→- **IP:** 208.109.235.224
|
||||
180→- **Hostname:** 224.235.109.208.host.secureserver.net
|
||||
181→- **Auth:** SSH key
|
||||
182→- **Database:** grabblaw_gdapp
|
||||
183→- **Note:** Old server, data migrated to IX
|
||||
184→
|
||||
185→---
|
||||
186→
|
||||
187→## Seafile (on Jupiter - Migrated 2025-12-27)
|
||||
188→
|
||||
189→### Container
|
||||
190→- **Host:** Jupiter (172.16.3.20)
|
||||
191→- **URL:** https://sync.azcomputerguru.com
|
||||
192→- **Port:** 8082 (internal), proxied via NPM
|
||||
193→- **Containers:** seafile, seafile-mysql, seafile-memcached, seafile-elasticsearch
|
||||
194→- **Docker Compose:** /mnt/user0/SeaFile/DockerCompose/docker-compose.yml
|
||||
195→- **Data Path:** /mnt/user0/SeaFile/seafile-data/
|
||||
196→
|
||||
197→### Seafile Admin
|
||||
198→- **Email:** mike@azcomputerguru.com
|
||||
199→- **Password:** r3tr0gradE99#
|
||||
200→
|
||||
201→### Database (MariaDB)
|
||||
202→- **Container:** seafile-mysql
|
||||
203→- **Image:** mariadb:10.6
|
||||
204→- **Root Password:** db_dev
|
||||
205→- **Seafile User:** seafile
|
||||
206→- **Seafile Password:** 64f2db5e-6831-48ed-a243-d4066fe428f9
|
||||
207→- **Databases:** ccnet_db (users), seafile_db (data), seahub_db (web)
|
||||
208→
|
||||
209→### Elasticsearch
|
||||
210→- **Container:** seafile-elasticsearch
|
||||
211→- **Image:** elasticsearch:7.17.26
|
||||
212→- **Note:** Upgraded from 7.16.2 for kernel 6.12 compatibility
|
||||
213→
|
||||
214→### Microsoft Graph API (Email)
|
||||
215→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
216→- **Client ID:** 15b0fafb-ab51-4cc9-adc7-f6334c805c22
|
||||
217→- **Client Secret:** rRN8Q~FPfSL8O24iZthi_LVJTjGOCZG.DnxGHaSk
|
||||
218→- **Sender Email:** noreply@azcomputerguru.com
|
||||
219→- **Used for:** Seafile email notifications via Graph API
|
||||
220→
|
||||
221→### Migration Notes
|
||||
222→- **Migrated from:** Saturn (172.16.3.21) on 2025-12-27
|
||||
223→- **Saturn Status:** Seafile stopped, data intact for rollback (keep 1 week)
|
||||
224→
|
||||
225→---
|
||||
226→
|
||||
227→## NPM Proxy Hosts Reference
|
||||
228→
|
||||
229→| ID | Domain | Backend | SSL Cert |
|
||||
230→|----|--------|---------|----------|
|
||||
231→| 1 | emby.azcomputerguru.com | 172.16.2.99:8096 | npm-1 |
|
||||
232→| 2 | git.azcomputerguru.com | 172.16.3.20:3000 | npm-2 |
|
||||
233→| 4 | plexrequest.azcomputerguru.com | 172.16.3.31:5055 | npm-4 |
|
||||
234→| 5 | rmm-api.azcomputerguru.com | 172.16.3.20:3001 | npm-6 |
|
||||
235→| - | unifi.azcomputerguru.com | 172.16.3.28:8443 | npm-5 |
|
||||
236→| 8 | sync.azcomputerguru.com | 172.16.3.20:8082 | npm-8 |
|
||||
237→
|
||||
238→---
|
||||
239→
|
||||
240→## Tailscale Network
|
||||
241→
|
||||
242→| Tailscale IP | Hostname | Owner | OS |
|
||||
243→|--------------|----------|-------|-----|
|
||||
244→| 100.79.69.82 (pfsense-1) | pfsense | mike@ | freebsd |
|
||||
245→| 100.125.36.6 | acg-m-l5090 | mike@ | windows |
|
||||
246→| 100.92.230.111 | acg-tech-01l | mike@ | windows |
|
||||
247→| 100.96.135.117 | acg-tech-02l | mike@ | windows |
|
||||
248→| 100.113.45.7 | acg-tech03l | howard@ | windows |
|
||||
249→| 100.77.166.22 | desktop-hjfjtep | mike@ | windows |
|
||||
250→| 100.101.145.100 | guru-legion9 | mike@ | windows |
|
||||
251→| 100.119.194.51 | guru-surface8 | howard@ | windows |
|
||||
252→| 100.66.103.110 | magus-desktop | rob@ | windows |
|
||||
253→| 100.66.167.120 | magus-pc | rob@ | windows |
|
||||
254→
|
||||
255→---
|
||||
256→
|
||||
257→## SSH Public Keys
|
||||
258→
|
||||
259→### guru@wsl (Windows/WSL)
|
||||
260→- **User:** guru
|
||||
261→- **Sudo Password:** Window123!@#-wsl
|
||||
262→- **SSH Key:**
|
||||
263→```
|
||||
264→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWY+SdqMHJP5JOe3qpWENQZhXJA4tzI2d7ZVNAwA/1u guru@wsl
|
||||
265→```
|
||||
266→
|
||||
267→### azcomputerguru@local (Mac)
|
||||
268→- **User:** azcomputerguru
|
||||
269→- **SSH Key:**
|
||||
270→```
|
||||
271→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDrGbr4EwvQ4P3ZtyZW3ZKkuDQOMbqyAQUul2+JE4K4S azcomputerguru@local
|
||||
272→```
|
||||
273→
|
||||
274→---
|
||||
275→
|
||||
276→## Quick Reference Commands
|
||||
277→
|
||||
278→### NPM API Auth
|
||||
279→```bash
|
||||
280→curl -s -X POST http://172.16.3.20:7818/api/tokens \
|
||||
281→ -H "Content-Type: application/json" \
|
||||
282→ -d '{"identity":"mike@azcomputerguru.com","secret":"Paper123!@#-unifi"}'
|
||||
283→```
|
||||
284→
|
||||
285→### Gitea API
|
||||
286→```bash
|
||||
287→curl -H "Authorization: token 9b1da4b79a38ef782268341d25a4b6880572063f" \
|
||||
288→ https://git.azcomputerguru.com/api/v1/repos/search
|
||||
289→```
|
||||
290→
|
||||
291→### GuruRMM Health Check
|
||||
292→```bash
|
||||
293→curl http://172.16.3.20:3001/health
|
||||
294→```
|
||||
295→
|
||||
296→---
|
||||
297→
|
||||
298→## MSP Tools
|
||||
299→
|
||||
300→### Syncro (PSA/RMM) - AZ Computer Guru
|
||||
301→- **API Key:** T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3
|
||||
302→- **Subdomain:** computerguru
|
||||
303→- **API Base URL:** https://computerguru.syncromsp.com/api/v1
|
||||
304→- **API Docs:** https://api-docs.syncromsp.com/
|
||||
305→- **Account:** AZ Computer Guru MSP
|
||||
306→- **Notes:** Added 2025-12-18
|
||||
307→
|
||||
308→### Autotask (PSA) - AZ Computer Guru
|
||||
309→- **API Username:** dguyqap2nucge6r@azcomputerguru.com
|
||||
310→- **API Password:** z*6G4fT#oM~8@9Hxy$2Y7K$ma
|
||||
311→- **API Integration Code:** HYTYYZ6LA5HB5XK7IGNA7OAHQLH
|
||||
312→- **Integration Name:** ClaudeAPI
|
||||
313→- **API Zone:** webservices5.autotask.net
|
||||
314→- **API Docs:** https://autotask.net/help/developerhelp/Content/APIs/REST/REST_API_Home.htm
|
||||
315→- **Account:** AZ Computer Guru MSP
|
||||
316→- **Notes:** Added 2025-12-18, new API user "Claude API"
|
||||
317→
|
||||
318→### CIPP (CyberDrain Improved Partner Portal)
|
||||
319→- **URL:** https://cippcanvb.azurewebsites.net
|
||||
320→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
321→- **API Client Name:** ClaudeCipp2 (working)
|
||||
322→- **App ID (Client ID):** 420cb849-542d-4374-9cb2-3d8ae0e1835b
|
||||
323→- **Client Secret:** MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT
|
||||
324→- **Scope:** api://420cb849-542d-4374-9cb2-3d8ae0e1835b/.default
|
||||
325→- **CIPP-SAM App ID:** 91b9102d-bafd-43f8-b17a-f99479149b07
|
||||
326→- **IP Range:** 0.0.0.0/0 (all IPs allowed)
|
||||
327→- **Auth Method:** OAuth 2.0 Client Credentials
|
||||
328→- **Notes:** Updated 2025-12-23, working API client
|
||||
329→
|
||||
330→#### CIPP API Usage (Bash)
|
||||
331→```bash
|
||||
332→# Get token
|
||||
333→ACCESS_TOKEN=$(curl -s -X POST "https://login.microsoftonline.com/ce61461e-81a0-4c84-bb4a-7b354a9a356d/oauth2/v2.0/token" \
|
||||
334→ -d "client_id=420cb849-542d-4374-9cb2-3d8ae0e1835b" \
|
||||
335→ -d "client_secret=MOn8Q~otmxJPLvmL~_aCVTV8Va4t4~SrYrukGbJT" \
|
||||
336→ -d "scope=api://420cb849-542d-4374-9cb2-3d8ae0e1835b/.default" \
|
||||
337→ -d "grant_type=client_credentials" | python3 -c "import sys, json; print(json.load(sys.stdin).get('access_token', ''))")
|
||||
338→
|
||||
339→# Query endpoints (use tenant domain or tenant ID as TenantFilter)
|
||||
340→curl -s "https://cippcanvb.azurewebsites.net/api/ListLicenses?TenantFilter=sonorangreenllc.com" \
|
||||
341→ -H "Authorization: Bearer ${ACCESS_TOKEN}"
|
||||
342→
|
||||
343→# Other useful endpoints:
|
||||
344→# ListTenants?AllTenants=true - List all managed tenants
|
||||
345→# ListUsers?TenantFilter={tenant} - List users
|
||||
346→# ListMailboxRules?TenantFilter={tenant} - Check mailbox rules
|
||||
347→# BECCheck?TenantFilter={tenant}&UserID={userid} - BEC investigation
|
||||
348→```
|
||||
349→
|
||||
350→#### Old API Client (403 errors - do not use)
|
||||
351→- **App ID:** d545a836-7118-44f6-8852-d9dd64fb7bb9
|
||||
352→- **Status:** Authenticated but all endpoints returned 403
|
||||
353→
|
||||
354→---
|
||||
355→
|
||||
356→## Client - MVAN Inc
|
||||
357→
|
||||
358→### Microsoft 365 Tenant 1
|
||||
359→- **Tenant:** mvan.onmicrosoft.com
|
||||
360→- **Admin User:** sysadmin@mvaninc.com
|
||||
361→- **Password:** r3tr0gradE99#
|
||||
362→- **Notes:** Global admin, project to merge/trust with T2
|
||||
363→
|
||||
364→---
|
||||
365→
|
||||
366→## Client - BG Builders LLC
|
||||
367→
|
||||
368→### Microsoft 365 Tenant
|
||||
369→- **Tenant:** bgbuildersllc.com
|
||||
370→- **CIPP Name:** sonorangreenllc.com
|
||||
371→- **Tenant ID:** ededa4fb-f6eb-4398-851d-5eb3e11fab27
|
||||
372→- **Admin User:** sysadmin@bgbuildersllc.com
|
||||
373→- **Password:** Window123!@#-bgb
|
||||
374→- **Notes:** Added 2025-12-19
|
||||
375→
|
||||
376→### Security Investigation (2025-12-22)
|
||||
377→- **Compromised User:** Shelly@bgbuildersllc.com (Shelly Dooley)
|
||||
378→- **Symptoms:** Suspicious sent items reported by user
|
||||
379→- **Findings:**
|
||||
380→ - Gmail OAuth app with EAS.AccessAsUser.All (REMOVED)
|
||||
381→ - "P2P Server" app registration backdoor (DELETED by admin)
|
||||
382→ - No malicious mailbox rules or forwarding
|
||||
383→ - Sign-in logs unavailable (no Entra P1 license)
|
||||
384→- **Remediation:**
|
||||
385→ - Password reset: `5ecwyHv6&dP7` (must change on login)
|
||||
386→ - All sessions revoked
|
||||
387→ - Gmail OAuth consent removed
|
||||
388→ - P2P Server backdoor deleted
|
||||
389→- **Status:** RESOLVED
|
||||
390→
|
||||
391→---
|
||||
392→
|
||||
393→## Client - Dataforth
|
||||
394→
|
||||
395→### Network
|
||||
396→- **Subnet:** 192.168.0.0/24
|
||||
397→- **Domain:** INTRANET (intranet.dataforth.com)
|
||||
398→
|
||||
399→### UDM (Unifi Dream Machine)
|
||||
400→- **IP:** 192.168.0.254
|
||||
401→- **SSH User:** root
|
||||
402→- **SSH Password:** Paper123!@#-unifi
|
||||
403→- **Web User:** azcomputerguru
|
||||
404→- **Web Password:** Paper123!@#-unifi
|
||||
405→- **2FA:** Push notification enabled
|
||||
406→- **Notes:** Gateway/firewall, OpenVPN server
|
||||
407→
|
||||
408→### AD1 (Domain Controller)
|
||||
409→- **IP:** 192.168.0.27
|
||||
410→- **Hostname:** AD1.intranet.dataforth.com
|
||||
411→- **User:** INTRANET\sysadmin
|
||||
412→- **Password:** Paper123!@#
|
||||
413→- **Role:** Primary DC, NPS/RADIUS server
|
||||
414→- **NPS Ports:** 1812/1813 (auth/accounting)
|
||||
415→
|
||||
416→### AD2 (Domain Controller)
|
||||
417→- **IP:** 192.168.0.6
|
||||
418→- **Hostname:** AD2.intranet.dataforth.com
|
||||
419→- **User:** INTRANET\sysadmin
|
||||
420→- **Password:** Paper123!@#
|
||||
421→- **Role:** Secondary DC, file server
|
||||
422→
|
||||
423→### NPS RADIUS Configuration
|
||||
424→- **Client Name:** unifi
|
||||
425→- **Client IP:** 192.168.0.254
|
||||
426→- **Shared Secret:** Gptf*77ttb!@#!@#
|
||||
427→- **Policy:** "Unifi" - allows Domain Users
|
||||
428→
|
||||
429→### D2TESTNAS (SMB1 Proxy)
|
||||
430→- **IP:** 192.168.0.9
|
||||
431→- **Web/SSH User:** admin
|
||||
432→- **Web/SSH Password:** Paper123!@#-nas
|
||||
433→- **Role:** DOS machine SMB1 proxy
|
||||
434→- **Notes:** Added 2025-12-14
|
||||
435→
|
||||
436→---
|
||||
437→
|
||||
438→## Client - Valley Wide Plastering
|
||||
439→
|
||||
440→### Network
|
||||
441→- **Subnet:** 172.16.9.0/24
|
||||
442→
|
||||
443→### UDM (UniFi Dream Machine)
|
||||
444→- **IP:** 172.16.9.1
|
||||
445→- **SSH User:** root
|
||||
446→- **SSH Password:** Gptf*77ttb123!@#-vwp
|
||||
447→- **Notes:** Gateway/firewall, VPN server, RADIUS client
|
||||
448→
|
||||
449→### VWP-DC1 (Domain Controller)
|
||||
450→- **IP:** 172.16.9.2
|
||||
451→- **Hostname:** VWP-DC1
|
||||
452→- **User:** sysadmin
|
||||
453→- **Password:** r3tr0gradE99#
|
||||
454→- **Role:** Primary DC, NPS/RADIUS server
|
||||
455→- **Notes:** Added 2025-12-22
|
||||
456→
|
||||
457→### NPS RADIUS Configuration
|
||||
458→- **RADIUS Server:** 172.16.9.2
|
||||
459→- **RADIUS Ports:** 1812 (auth), 1813 (accounting)
|
||||
460→- **Clients:** UDM (172.16.9.1), VWP-Subnet (172.16.9.0/24)
|
||||
461→- **Shared Secret:** Gptf*77ttb123!@#-radius
|
||||
462→- **Policy:** "VPN-Access" - allows all authenticated users (24/7)
|
||||
463→- **Auth Methods:** All (PAP, CHAP, MS-CHAP, MS-CHAPv2, EAP)
|
||||
464→- **User Dial-in:** All VWP_Users set to Allow
|
||||
465→- **AuthAttributeRequired:** Disabled on clients
|
||||
466→- **Tested:** 2025-12-22, user cguerrero authenticated successfully
|
||||
467→
|
||||
468→### Dataforth - Entra App Registration (Claude-Code-M365)
|
||||
469→- **Tenant ID:** 7dfa3ce8-c496-4b51-ab8d-bd3dcd78b584
|
||||
470→- **App ID (Client ID):** 7a8c0b2e-57fb-4d79-9b5a-4b88d21b1f29
|
||||
471→- **Client Secret:** tXo8Q~ZNG9zoBpbK9HwJTkzx.YEigZ9AynoSrca3
|
||||
472→- **Permissions:** Calendars.ReadWrite, Contacts.ReadWrite, User.ReadWrite.All, Mail.ReadWrite, Directory.ReadWrite.All, Group.ReadWrite.All
|
||||
473→- **Created:** 2025-12-22
|
||||
474→- **Use:** Silent Graph API access to Dataforth tenant
|
||||
475→
|
||||
476→---
|
||||
477→
|
||||
478→## Client - CW Concrete LLC
|
||||
479→
|
||||
480→### Microsoft 365 Tenant
|
||||
481→- **Tenant:** cwconcretellc.com
|
||||
482→- **CIPP Name:** cwconcretellc.com
|
||||
483→- **Tenant ID:** dfee2224-93cd-4291-9b09-6c6ce9bb8711
|
||||
484→- **Default Domain:** NETORGFT11452752.onmicrosoft.com
|
||||
485→- **Notes:** De-federated from GoDaddy 2025-12, domain needs re-verification
|
||||
486→
|
||||
487→### Security Investigation (2025-12-22)
|
||||
488→- **Findings:**
|
||||
489→ - Graph Command Line Tools OAuth consent with high privileges (REMOVED)
|
||||
490→ - "test" backdoor app registration with multi-tenant access (DELETED)
|
||||
491→ - Apple Internet Accounts OAuth (left - likely iOS device)
|
||||
492→ - No malicious mailbox rules or forwarding
|
||||
493→- **Remediation:**
|
||||
494→ - All sessions revoked for all 4 users
|
||||
495→ - Backdoor apps removed
|
||||
496→- **Status:** RESOLVED
|
||||
497→
|
||||
498→---
|
||||
499→
|
||||
500→## Client - Khalsa
|
||||
501→
|
||||
502→### Network
|
||||
503→- **Subnet:** 172.16.50.0/24
|
||||
504→
|
||||
505→### UCG (UniFi Cloud Gateway)
|
||||
506→- **IP:** 172.16.50.1
|
||||
507→- **SSH User:** azcomputerguru
|
||||
508→- **SSH Password:** Paper123!@#-camden (reset 2025-12-22)
|
||||
509→- **Notes:** Gateway/firewall, VPN server, SSH key added but not working
|
||||
510→
|
||||
511→### Switch
|
||||
512→- **User:** 8WfY8
|
||||
513→- **Password:** tI3evTNBZMlnngtBc
|
||||
514→
|
||||
515→### Accountant Machine
|
||||
516→- **IP:** 172.16.50.168
|
||||
517→- **User:** accountant
|
||||
518→- **Password:** Paper123!@#-accountant
|
||||
519→- **Notes:** Added 2025-12-22, VPN routing issue
|
||||
520→
|
||||
521→---
|
||||
522→
|
||||
523→## Client - Scileppi Law Firm
|
||||
524→
|
||||
525→### DS214se (Source NAS - being migrated)
|
||||
526→- **IP:** 172.16.1.54
|
||||
527→- **SSH User:** admin
|
||||
528→- **Password:** Th1nk3r^99
|
||||
529→- **Storage:** 1.8TB (1.6TB used)
|
||||
530→- **Data:** User home folders (admin, Andrew Ross, Chris Scileppi, Samantha Nunez, etc.)
|
||||
531→
|
||||
532→### Unraid (Source - Migration)
|
||||
533→- **IP:** 172.16.1.21
|
||||
534→- **SSH User:** root
|
||||
535→- **Password:** Th1nk3r^99
|
||||
536→- **Role:** Data source for migration to RS2212+
|
||||
537→
|
||||
538→### RS2212+ (Destination NAS)
|
||||
539→- **IP:** 172.16.1.59
|
||||
540→- **Hostname:** SL-SERVER
|
||||
541→- **SSH User:** sysadmin
|
||||
542→- **Password:** Gptf*77ttb123!@#-sl-server
|
||||
543→- **SSH Key:** claude-code@localadmin added to authorized_keys
|
||||
544→- **Storage:** 25TB available
|
||||
545→- **Notes:** User home service enabled, migration in progress 2025-12-23
|
||||
546→
|
||||
547→### Migration Status
|
||||
548→- **Started:** 2025-12-23
|
||||
549→- **Method:** rsync over SSH (RS2212+ pulling from DS214se)
|
||||
550→- **Command:** `rsync -avz --progress -e 'ssh -i ~/.ssh/id_ed25519' admin@172.16.1.54:/volume1/homes/ /volume1/homes/`
|
||||
551→- **Estimated time:** 10-20 hours for 1.6TB
|
||||
552→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,17 @@
|
||||
674- '<div style="display: flex; align-items: center; gap: 12px;">' +
|
||||
675- '<div style="width: 10px; height: 10px; border-radius: 50%; background: hsl(142, 76%, 50%);"></div>' +
|
||||
676- '<div>' +
|
||||
677: '<div style="font-weight: 500;">' + (m.agent_name || m.agent_id.slice(0,8)) + '</div>' +
|
||||
678- '<div style="font-size: 12px; color: hsl(var(--muted-foreground));">Connected ' + started + '</div>' +
|
||||
679- '</div>' +
|
||||
680- '</div>' +
|
||||
--
|
||||
710- '<div class="detail-section">' +
|
||||
711- '<div class="detail-section-title">Actions</div>' +
|
||||
712- '<button class="btn btn-primary" style="width: 100%; margin-bottom: 8px;" onclick="connectToMachine(\'' + m.id + '\')">Connect</button>' +
|
||||
713: '<button class="btn btn-outline" style="width: 100%; margin-bottom: 8px;" onclick="openChat(\'' + m.id + '\', \'' + (m.agent_name || 'Client').replace(/'/g, "\\'") + '\')">Chat</button>' +
|
||||
714- '<button class="btn btn-outline" style="width: 100%; margin-bottom: 8px;" disabled>Transfer Files</button>' +
|
||||
715: '<button class="btn btn-outline" style="width: 100%; color: hsl(0, 62.8%, 50%);" onclick="disconnectMachine(\'' + m.id + '\', \'' + (m.agent_name || m.agent_id).replace(/'/g, "\\'") + '\')">Disconnect</button>' +
|
||||
716- '</div>';
|
||||
717- }
|
||||
718-
|
||||
@@ -0,0 +1,389 @@
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: /tmp/guru-connect/agent/Cargo.toml
|
||||
workspace: /tmp/guru-connect/Cargo.toml
|
||||
warning: profiles for the non root package will be ignored, specify profiles at the workspace root:
|
||||
package: /tmp/guru-connect/server/Cargo.toml
|
||||
workspace: /tmp/guru-connect/Cargo.toml
|
||||
Compiling proc-macro2 v1.0.103
|
||||
Compiling libc v0.2.178
|
||||
Compiling cfg-if v1.0.4
|
||||
Compiling quote v1.0.42
|
||||
Compiling serde_core v1.0.228
|
||||
Compiling serde v1.0.228
|
||||
Compiling generic-array v0.14.7
|
||||
Compiling parking_lot_core v0.9.12
|
||||
Compiling autocfg v1.5.0
|
||||
Compiling bytes v1.11.0
|
||||
Compiling icu_normalizer_data v2.1.1
|
||||
Compiling itoa v1.0.16
|
||||
Compiling icu_properties_data v2.1.2
|
||||
Compiling pin-project-lite v0.2.16
|
||||
Compiling typenum v1.19.0
|
||||
Compiling zerocopy v0.8.31
|
||||
Compiling stable_deref_trait v1.2.1
|
||||
Compiling syn v2.0.111
|
||||
Compiling futures-core v0.3.31
|
||||
Compiling crossbeam-utils v0.8.21
|
||||
Compiling scopeguard v1.2.0
|
||||
Compiling lock_api v0.4.14
|
||||
Compiling subtle v2.6.1
|
||||
Compiling futures-sink v0.3.31
|
||||
Compiling thiserror v2.0.17
|
||||
Compiling serde_json v1.0.145
|
||||
Compiling signal-hook-registry v1.4.7
|
||||
Compiling socket2 v0.6.1
|
||||
Compiling mio v1.1.1
|
||||
Compiling block-buffer v0.10.4
|
||||
Compiling crypto-common v0.1.7
|
||||
Compiling digest v0.10.7
|
||||
Compiling memchr v2.7.6
|
||||
Compiling getrandom v0.2.16
|
||||
Compiling getrandom v0.3.4
|
||||
Compiling writeable v0.6.2
|
||||
Compiling litemap v0.8.1
|
||||
Compiling anyhow v1.0.100
|
||||
Compiling log v0.4.29
|
||||
Compiling equivalent v1.0.2
|
||||
Compiling num-traits v0.2.19
|
||||
Compiling once_cell v1.21.3
|
||||
Compiling pin-utils v0.1.0
|
||||
Compiling tracing-core v0.1.36
|
||||
Compiling hashbrown v0.16.1
|
||||
Compiling rand_core v0.6.4
|
||||
Compiling http v1.4.0
|
||||
Compiling futures-io v0.3.31
|
||||
Compiling indexmap v2.12.1
|
||||
Compiling slab v0.4.11
|
||||
Compiling bitflags v2.10.0
|
||||
Compiling futures-task v0.3.31
|
||||
Compiling cpufeatures v0.2.17
|
||||
Compiling percent-encoding v2.3.2
|
||||
Compiling utf8_iter v1.0.4
|
||||
Compiling tinyvec_macros v0.1.1
|
||||
Compiling httparse v1.10.1
|
||||
Compiling foldhash v0.1.5
|
||||
Compiling ryu v1.0.21
|
||||
Compiling allocator-api2 v0.2.21
|
||||
Compiling tinyvec v1.10.0
|
||||
Compiling synstructure v0.13.2
|
||||
Compiling hashbrown v0.15.5
|
||||
Compiling form_urlencoded v1.2.2
|
||||
Compiling tokio v1.48.0
|
||||
Compiling http-body v1.0.1
|
||||
Compiling ppv-lite86 v0.2.21
|
||||
Compiling concurrent-queue v2.5.0
|
||||
Compiling shlex v1.3.0
|
||||
Compiling rand_chacha v0.3.1
|
||||
Compiling base64 v0.22.1
|
||||
Compiling find-msvc-tools v0.1.5
|
||||
Compiling crc32fast v1.5.0
|
||||
Compiling parking v2.2.1
|
||||
Compiling heck v0.5.0
|
||||
Compiling rustix v1.1.2
|
||||
Compiling iana-time-zone v0.1.64
|
||||
Compiling crc-catalog v2.4.0
|
||||
Compiling crc v3.4.0
|
||||
Compiling futures-util v0.3.31
|
||||
Compiling chrono v0.4.42
|
||||
Compiling serde_derive v1.0.228
|
||||
Compiling zerofrom-derive v0.1.6
|
||||
Compiling yoke-derive v0.8.1
|
||||
Compiling zerovec-derive v0.11.2
|
||||
Compiling displaydoc v0.2.5
|
||||
Compiling tracing-attributes v0.1.31
|
||||
Compiling tokio-macros v2.6.0
|
||||
Compiling thiserror-impl v2.0.17
|
||||
Compiling zerofrom v0.1.6
|
||||
Compiling yoke v0.8.1
|
||||
Compiling zerovec v0.11.5
|
||||
Compiling zerotrie v0.2.3
|
||||
Compiling tinystr v0.8.2
|
||||
Compiling icu_locale_core v2.1.1
|
||||
Compiling potential_utf v0.1.4
|
||||
Compiling icu_collections v2.1.1
|
||||
Compiling futures-macro v0.3.31
|
||||
Compiling tracing v0.1.44
|
||||
Compiling icu_provider v2.1.1
|
||||
Compiling tokio-stream v0.1.17
|
||||
Compiling icu_properties v2.1.2
|
||||
Compiling either v1.15.0
|
||||
Compiling smallvec v1.15.1
|
||||
Compiling itertools v0.14.0
|
||||
Compiling icu_normalizer v2.1.1
|
||||
Compiling parking_lot v0.12.5
|
||||
Compiling idna_adapter v1.2.1
|
||||
Compiling idna v1.1.0
|
||||
Compiling url v2.5.7
|
||||
Compiling futures-intrusive v0.5.0
|
||||
Compiling sha2 v0.10.9
|
||||
Compiling event-listener v5.4.1
|
||||
Compiling prost-derive v0.13.5
|
||||
Compiling rand v0.8.5
|
||||
Compiling cc v1.2.50
|
||||
Compiling hashlink v0.10.0
|
||||
Compiling unicode-normalization v0.1.25
|
||||
Compiling crossbeam-queue v0.3.12
|
||||
Compiling hmac v0.12.1
|
||||
Compiling futures-channel v0.3.31
|
||||
Compiling byteorder v1.5.0
|
||||
Compiling linux-raw-sys v0.11.0
|
||||
Compiling regex-syntax v0.8.8
|
||||
Compiling unicode-bidi v0.3.18
|
||||
Compiling tower-service v0.3.3
|
||||
Compiling thiserror v1.0.69
|
||||
Compiling prettyplease v0.2.37
|
||||
Compiling uuid v1.19.0
|
||||
Compiling simd-adler32 v0.3.8
|
||||
Compiling unicode-properties v0.1.4
|
||||
Compiling adler2 v2.0.1
|
||||
Compiling stringprep v0.1.5
|
||||
Compiling miniz_oxide v0.8.9
|
||||
Compiling sqlx-core v0.8.6
|
||||
Compiling ring v0.17.14
|
||||
Compiling regex-automata v0.4.13
|
||||
Compiling prost v0.13.5
|
||||
Compiling hkdf v0.12.4
|
||||
Compiling thiserror-impl v1.0.69
|
||||
Compiling atoi v2.0.0
|
||||
Compiling md-5 v0.10.6
|
||||
Compiling rustversion v1.0.22
|
||||
Compiling num-conv v0.1.0
|
||||
Compiling home v0.5.12
|
||||
Compiling unicase v2.8.1
|
||||
Compiling fixedbitset v0.5.7
|
||||
Compiling hex v0.4.3
|
||||
Compiling time-core v0.1.6
|
||||
Compiling powerfmt v0.2.0
|
||||
Compiling fastrand v2.3.0
|
||||
Compiling whoami v1.6.1
|
||||
Compiling dotenvy v0.15.7
|
||||
Compiling mime v0.3.17
|
||||
Compiling httpdate v1.0.3
|
||||
Compiling tower-layer v0.3.3
|
||||
Compiling sqlx-postgres v0.8.6
|
||||
Compiling tempfile v3.23.0
|
||||
Compiling deranged v0.5.5
|
||||
Compiling time-macros v0.2.24
|
||||
Compiling petgraph v0.7.1
|
||||
Compiling mime_guess v2.0.5
|
||||
Compiling regex v1.12.2
|
||||
Compiling flate2 v1.1.5
|
||||
Compiling prost-types v0.13.5
|
||||
Compiling http-body-util v0.1.3
|
||||
Compiling num-integer v0.1.46
|
||||
Compiling sha1 v0.10.6
|
||||
Compiling sync_wrapper v1.0.2
|
||||
Compiling atomic-waker v1.1.2
|
||||
Compiling multimap v0.10.1
|
||||
Compiling utf-8 v0.7.6
|
||||
Compiling compression-core v0.4.31
|
||||
Compiling data-encoding v2.9.0
|
||||
Compiling compression-codecs v0.4.35
|
||||
Compiling tungstenite v0.24.0
|
||||
Compiling time v0.3.44
|
||||
Compiling sqlx-macros-core v0.8.6
|
||||
Compiling prost-build v0.13.5
|
||||
Compiling hyper v1.8.1
|
||||
Compiling num-bigint v0.4.6
|
||||
Compiling serde_spanned v0.6.9
|
||||
Compiling toml_datetime v0.6.3
|
||||
Compiling async-trait v0.1.89
|
||||
Compiling winnow v0.5.40
|
||||
Compiling lazy_static v1.5.0
|
||||
Compiling untrusted v0.9.0
|
||||
Compiling base64ct v1.8.1
|
||||
Compiling password-hash v0.5.0
|
||||
Compiling toml_edit v0.20.2
|
||||
Compiling sharded-slab v0.1.7
|
||||
Compiling axum-core v0.4.5
|
||||
Compiling simple_asn1 v0.6.3
|
||||
Compiling sqlx-macros v0.8.6
|
||||
Compiling guruconnect-server v0.1.0 (/tmp/guru-connect/server)
|
||||
Compiling hyper-util v0.1.19
|
||||
Compiling matchers v0.2.0
|
||||
Compiling tokio-tungstenite v0.24.0
|
||||
Compiling async-compression v0.4.36
|
||||
Compiling tower v0.5.2
|
||||
Compiling tokio-util v0.7.17
|
||||
Compiling serde_urlencoded v0.7.1
|
||||
Compiling axum-macros v0.4.2
|
||||
Compiling pem v3.0.6
|
||||
Compiling tracing-log v0.2.0
|
||||
Compiling serde_path_to_error v0.1.20
|
||||
Compiling blake2 v0.10.6
|
||||
Compiling thread_local v1.1.9
|
||||
Compiling matchit v0.7.3
|
||||
Compiling http-range-header v0.4.2
|
||||
Compiling nu-ansi-term v0.50.3
|
||||
Compiling tracing-subscriber v0.3.22
|
||||
Compiling tower-http v0.6.8
|
||||
Compiling axum v0.7.9
|
||||
Compiling argon2 v0.5.3
|
||||
Compiling jsonwebtoken v9.3.1
|
||||
Compiling sqlx v0.8.6
|
||||
Compiling toml v0.8.2
|
||||
warning: unused variable: `config`
|
||||
--> server/src/main.rs:54:9
|
||||
|
|
||||
54 | let config = config::Config::load()?;
|
||||
| ^^^^^^ help: if this is intentional, prefix it with an underscore: `_config`
|
||||
|
|
||||
= note: `#[warn(unused_variables)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: struct `ValidateParams` is never constructed
|
||||
--> server/src/main.rs:138:8
|
||||
|
|
||||
138 | struct ValidateParams {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default
|
||||
|
||||
warning: fields `listen_addr`, `database_url`, `jwt_secret`, and `debug` are never read
|
||||
--> server/src/config.rs:10:9
|
||||
|
|
||||
8 | pub struct Config {
|
||||
| ------ fields in this struct
|
||||
9 | /// Address to listen on (e.g., "0.0.0.0:8080")
|
||||
10 | pub listen_addr: String,
|
||||
| ^^^^^^^^^^^
|
||||
...
|
||||
13 | pub database_url: Option<String>,
|
||||
| ^^^^^^^^^^^^
|
||||
...
|
||||
16 | pub jwt_secret: Option<String>,
|
||||
| ^^^^^^^^^^
|
||||
...
|
||||
19 | pub debug: bool,
|
||||
| ^^^^^
|
||||
|
|
||||
= note: `Config` has derived impls for the traits `Clone` and `Debug`, but these are intentionally ignored during dead code analysis
|
||||
|
||||
warning: constant `HEARTBEAT_TIMEOUT_SECS` is never used
|
||||
--> server/src/session/mod.rs:22:7
|
||||
|
|
||||
22 | const HEARTBEAT_TIMEOUT_SECS: u64 = 90;
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `input_rx` is never read
|
||||
--> server/src/session/mod.rs:58:5
|
||||
|
|
||||
52 | struct SessionData {
|
||||
| ----------- field in this struct
|
||||
...
|
||||
58 | input_rx: Option<InputReceiver>,
|
||||
| ^^^^^^^^
|
||||
|
||||
warning: methods `is_session_timed_out`, `get_timed_out_sessions`, `get_session_by_agent`, and `remove_session` are never used
|
||||
--> server/src/session/mod.rs:185:18
|
||||
|
|
||||
72 | impl SessionManager {
|
||||
| ------------------- methods in this implementation
|
||||
...
|
||||
185 | pub async fn is_session_timed_out(&self, session_id: SessionId) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
195 | pub async fn get_timed_out_sessions(&self) -> Vec<SessionId> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
205 | pub async fn get_session_by_agent(&self, agent_id: &str) -> Option<Session> {
|
||||
| ^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
317 | pub async fn remove_session(&self, session_id: SessionId) {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `AuthenticatedUser` is never constructed
|
||||
--> server/src/auth/mod.rs:13:12
|
||||
|
|
||||
13 | pub struct AuthenticatedUser {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: struct `AuthenticatedAgent` is never constructed
|
||||
--> server/src/auth/mod.rs:21:12
|
||||
|
|
||||
21 | pub struct AuthenticatedAgent {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `validate_agent_key` is never used
|
||||
--> server/src/auth/mod.rs:54:8
|
||||
|
|
||||
54 | pub fn validate_agent_key(_api_key: &str) -> Option<AuthenticatedAgent> {
|
||||
| ^^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: function `list_sessions` is never used
|
||||
--> server/src/api/mod.rs:51:14
|
||||
|
|
||||
51 | pub async fn list_sessions(
|
||||
| ^^^^^^^^^^^^^
|
||||
|
||||
warning: function `get_session` is never used
|
||||
--> server/src/api/mod.rs:59:14
|
||||
|
|
||||
59 | pub async fn get_session(
|
||||
| ^^^^^^^^^^^
|
||||
|
||||
warning: struct `Database` is never constructed
|
||||
--> server/src/db/mod.rs:10:12
|
||||
|
|
||||
10 | pub struct Database {
|
||||
| ^^^^^^^^
|
||||
|
||||
warning: associated function `init` is never used
|
||||
--> server/src/db/mod.rs:17:18
|
||||
|
|
||||
15 | impl Database {
|
||||
| ------------- associated function in this implementation
|
||||
16 | /// Initialize database connection
|
||||
17 | pub async fn init(_database_url: &str) -> Result<Self> {
|
||||
| ^^^^
|
||||
|
||||
warning: struct `SessionEvent` is never constructed
|
||||
--> server/src/db/mod.rs:25:12
|
||||
|
|
||||
25 | pub struct SessionEvent {
|
||||
| ^^^^^^^^^^^^
|
||||
|
||||
warning: enum `SessionEventType` is never used
|
||||
--> server/src/db/mod.rs:32:10
|
||||
|
|
||||
32 | pub enum SessionEventType {
|
||||
| ^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: method `log_session_event` is never used
|
||||
--> server/src/db/mod.rs:41:18
|
||||
|
|
||||
39 | impl Database {
|
||||
| ------------- method in this implementation
|
||||
40 | /// Log a session event (placeholder)
|
||||
41 | pub async fn log_session_event(&self, _event: SessionEvent) -> Result<()> {
|
||||
| ^^^^^^^^^^^^^^^^^
|
||||
|
||||
warning: field `technician_id` is never read
|
||||
--> server/src/support_codes.rs:39:9
|
||||
|
|
||||
38 | pub struct CreateCodeRequest {
|
||||
| ----------------- field in this struct
|
||||
39 | pub technician_id: Option<String>,
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= note: `CreateCodeRequest` has a derived impl for the trait `Debug`, but this is intentionally ignored during dead code analysis
|
||||
|
||||
warning: methods `get_code`, `is_valid_for_connection`, `list_codes`, and `get_by_session` are never used
|
||||
--> server/src/support_codes.rs:166:18
|
||||
|
|
||||
60 | impl SupportCodeManager {
|
||||
| ----------------------- methods in this implementation
|
||||
...
|
||||
166 | pub async fn get_code(&self, code: &str) -> Option<SupportCode> {
|
||||
| ^^^^^^^^
|
||||
...
|
||||
198 | pub async fn is_valid_for_connection(&self, code: &str) -> bool {
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^
|
||||
...
|
||||
204 | pub async fn list_codes(&self) -> Vec<SupportCode> {
|
||||
| ^^^^^^^^^^
|
||||
...
|
||||
219 | pub async fn get_by_session(&self, session_id: Uuid) -> Option<SupportCode> {
|
||||
| ^^^^^^^^^^^^^^
|
||||
|
||||
warning: `guruconnect-server` (bin "guruconnect-server") generated 18 warnings (run `cargo fix --bin "guruconnect-server" -p guruconnect-server` to apply 1 suggestion)
|
||||
Finished `release` profile [optimized] target(s) in 57.14s
|
||||
@@ -0,0 +1,5 @@
|
||||
No binary found, checking target structure:
|
||||
build/
|
||||
deps/
|
||||
examples/
|
||||
incremental/
|
||||
@@ -0,0 +1,12 @@
|
||||
100.125.36.6 acg-m-l5090 mike@ windows -
|
||||
100.92.230.111 acg-tech-01l mike@ windows offline, last seen 6d ago
|
||||
100.96.135.117 acg-tech-02l mike@ windows offline, last seen 6d ago
|
||||
100.113.45.7 acg-tech03l howard@ windows -
|
||||
100.77.166.22 desktop-hjfjtep mike@ windows -
|
||||
100.101.145.100 guru-legion9 mike@ windows offline, last seen 50d ago
|
||||
100.119.194.51 guru-surface8 howard@ windows offline, last seen 81d ago
|
||||
100.66.103.110 magus-desktop rob@ windows offline, last seen 58d ago
|
||||
100.66.167.120 magus-pc rob@ windows offline, last seen 146d ago
|
||||
100.65.158.123 mikes-macbook-air mike@ macOS offline, last seen 12m ago
|
||||
100.119.153.74 pfsense-2 mike@ freebsd active; direct 98.181.90.163:12101, tx 13175936 rx 69340004
|
||||
100.122.110.39 pfsense mike@ freebsd offline, last seen 2d ago
|
||||
@@ -0,0 +1,4 @@
|
||||
username | role | enabled
|
||||
----------+-------+---------
|
||||
admin | admin | t
|
||||
(1 row)
|
||||
@@ -0,0 +1 @@
|
||||
No matches found
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 1
|
||||
tail: cannot open 'server.log' for reading: No such file or directory
|
||||
@@ -0,0 +1,26 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\api\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
8→use uuid::Uuid;
|
||||
9→
|
||||
10→use crate::session::SessionManager;
|
||||
11→
|
||||
12→/// Session info returned by API
|
||||
13→#[derive(Debug, Serialize)]
|
||||
14→pub struct SessionInfo {
|
||||
15→ pub id: String,
|
||||
16→ pub agent_id: String,
|
||||
17→ pub agent_name: String,
|
||||
18→ pub started_at: String,
|
||||
19→ pub viewer_count: usize,
|
||||
20→ pub is_streaming: bool,
|
||||
21→ pub is_online: bool,
|
||||
22→ pub is_persistent: bool,
|
||||
23→ pub last_heartbeat: String,
|
||||
24→ pub os_version: Option<String>,
|
||||
25→ pub is_elevated: bool,
|
||||
26→ pub uptime_secs: i64,
|
||||
27→ pub display_count: i32,
|
||||
28→}
|
||||
29→
|
||||
30→impl From<crate::session::Session> for SessionInfo {
|
||||
31→ fn from(s: crate::session::Session) -> Self {
|
||||
32→ Self {
|
||||
@@ -0,0 +1,104 @@
|
||||
660→ }
|
||||
661→
|
||||
662→ function renderMachinesList() {
|
||||
663→ const container = document.getElementById("machinesList");
|
||||
664→
|
||||
665→ if (machines.length === 0) {
|
||||
666→ container.innerHTML = '<div class="empty-state"><h3>No machines</h3><p>Install the agent on a machine to see it here</p></div>';
|
||||
667→ return;
|
||||
668→ }
|
||||
669→
|
||||
670→ container.innerHTML = '<div style="padding: 12px;">' + machines.map(m => {
|
||||
671→ const started = new Date(m.started_at).toLocaleString();
|
||||
672→ const isSelected = selectedMachine?.id === m.id;
|
||||
673→ return '<div class="sidebar-item' + (isSelected ? ' active' : '') + '" onclick="selectMachine(\'' + m.id + '\')" style="margin-bottom: 8px; padding: 12px;">' +
|
||||
674→ '<div style="display: flex; align-items: center; gap: 12px;">' +
|
||||
675→ '<div style="width: 10px; height: 10px; border-radius: 50%; background: hsl(142, 76%, 50%);"></div>' +
|
||||
676→ '<div>' +
|
||||
677→ '<div style="font-weight: 500;">' + (m.agent_name || m.agent_id.slice(0,8)) + '</div>' +
|
||||
678→ '<div style="font-size: 12px; color: hsl(var(--muted-foreground));">Connected ' + started + '</div>' +
|
||||
679→ '</div>' +
|
||||
680→ '</div>' +
|
||||
681→ '</div>';
|
||||
682→ }).join("") + '</div>';
|
||||
683→ }
|
||||
684→
|
||||
685→ function selectMachine(id) {
|
||||
686→ selectedMachine = machines.find(m => m.id === id);
|
||||
687→ renderMachinesList();
|
||||
688→ renderMachineDetail();
|
||||
689→ }
|
||||
690→
|
||||
691→ function renderMachineDetail() {
|
||||
692→ const container = document.getElementById("machineDetail");
|
||||
693→
|
||||
694→ if (!selectedMachine) {
|
||||
695→ container.innerHTML = '<div class="empty-state"><h3>Select a machine</h3><p>Click a machine to view details</p></div>';
|
||||
696→ return;
|
||||
697→ }
|
||||
698→
|
||||
699→ const m = selectedMachine;
|
||||
700→ const started = new Date(m.started_at).toLocaleString();
|
||||
701→
|
||||
702→ container.innerHTML =
|
||||
703→ '<div class="detail-section">' +
|
||||
704→ '<div class="detail-section-title">Machine Info</div>' +
|
||||
705→ '<div class="detail-row"><span class="detail-label">Agent ID</span><span class="detail-value">' + m.agent_id.slice(0,8) + '...</span></div>' +
|
||||
706→ '<div class="detail-row"><span class="detail-label">Session ID</span><span class="detail-value">' + m.id.slice(0,8) + '...</span></div>' +
|
||||
707→ '<div class="detail-row"><span class="detail-label">Connected</span><span class="detail-value">' + started + '</span></div>' +
|
||||
708→ '<div class="detail-row"><span class="detail-label">Viewers</span><span class="detail-value">' + m.viewer_count + '</span></div>' +
|
||||
709→ '</div>' +
|
||||
710→ '<div class="detail-section">' +
|
||||
711→ '<div class="detail-section-title">Actions</div>' +
|
||||
712→ '<button class="btn btn-primary" style="width: 100%; margin-bottom: 8px;" onclick="connectToMachine(\'' + m.id + '\')">Connect</button>' +
|
||||
713→ '<button class="btn btn-outline" style="width: 100%; margin-bottom: 8px;" onclick="openChat(\'' + m.id + '\', \'' + (m.agent_name || 'Client').replace(/'/g, "\\'") + '\')">Chat</button>' +
|
||||
714→ '<button class="btn btn-outline" style="width: 100%; margin-bottom: 8px;" disabled>Transfer Files</button>' +
|
||||
715→ '<button class="btn btn-outline" style="width: 100%; color: hsl(0, 62.8%, 50%);" onclick="disconnectMachine(\'' + m.id + '\', \'' + (m.agent_name || m.agent_id).replace(/'/g, "\\'") + '\')">Disconnect</button>' +
|
||||
716→ '</div>';
|
||||
717→ }
|
||||
718→
|
||||
719→ function connectToMachine(sessionId) {
|
||||
720→ // Open viewer in new window
|
||||
721→ const viewerUrl = "/viewer.html?session_id=" + sessionId;
|
||||
722→ window.open(viewerUrl, "viewer_" + sessionId, "width=1280,height=800,menubar=no,toolbar=no,location=no,status=no");
|
||||
723→ }
|
||||
724→
|
||||
725→ async function disconnectMachine(sessionId, machineName) {
|
||||
726→ if (!confirm("Disconnect " + machineName + "?\\n\\nThis will end the remote session.")) return;
|
||||
727→ try {
|
||||
728→ const response = await fetch("/api/sessions/" + sessionId, { method: "DELETE" });
|
||||
729→ if (response.ok) {
|
||||
730→ selectedMachine = null;
|
||||
731→ renderMachineDetail();
|
||||
732→ loadMachines();
|
||||
733→ } else {
|
||||
734→ alert("Failed to disconnect: " + await response.text());
|
||||
735→ }
|
||||
736→ } catch (err) {
|
||||
737→ alert("Error disconnecting machine");
|
||||
738→ }
|
||||
739→ }
|
||||
740→
|
||||
741→ // Refresh machines every 5 seconds
|
||||
742→ loadMachines();
|
||||
743→ setInterval(loadMachines, 5000);
|
||||
744→
|
||||
745→ // ========== Chat Functions ==========
|
||||
746→
|
||||
747→ // Chat modal elements
|
||||
748→ const chatModal = document.getElementById("chatModal");
|
||||
749→ const chatMessagesEl = document.getElementById("chatMessages");
|
||||
750→ const chatInput = document.getElementById("chatInput");
|
||||
751→ const chatSend = document.getElementById("chatSend");
|
||||
752→ const chatClose = document.getElementById("chatClose");
|
||||
753→ const chatClientName = document.getElementById("chatClientName");
|
||||
754→
|
||||
755→ // Close chat modal
|
||||
756→ chatClose.addEventListener("click", () => {
|
||||
757→ closeChat();
|
||||
758→ });
|
||||
759→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 255
|
||||
root@172.16.3.20: Permission denied (publickey,password,keyboard-interactive).
|
||||
@@ -0,0 +1,5 @@
|
||||
Host ix.azcomputerguru.com
|
||||
User root
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
BatchMode yes
|
||||
@@ -0,0 +1,24 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\static\dashboard.html has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
666→ container.innerHTML = '<div class="empty-state"><h3>No machines</h3><p>Install the agent on a machine to see it here</p></div>';
|
||||
667→ return;
|
||||
668→ }
|
||||
669→
|
||||
670→ container.innerHTML = '<div style="padding: 12px;">' + machines.map(m => {
|
||||
671→ const started = new Date(m.started_at).toLocaleString();
|
||||
672→ const isSelected = selectedMachine?.id === m.id;
|
||||
673→ const statusColor = m.is_online ? 'hsl(142, 76%, 50%)' : 'hsl(0, 0%, 50%)';
|
||||
674→ const statusText = m.is_online ? 'Online' : 'Offline';
|
||||
675→ return '<div class="sidebar-item' + (isSelected ? ' active' : '') + '" onclick="selectMachine(\'' + m.id + '\')" style="margin-bottom: 8px; padding: 12px;">' +
|
||||
676→ '<div style="display: flex; align-items: center; gap: 12px;">' +
|
||||
677→ '<div style="width: 10px; height: 10px; border-radius: 50%; background: ' + statusColor + ';"></div>' +
|
||||
678→ '<div>' +
|
||||
679→ '<div style="font-weight: 500;">' + (m.agent_name || m.agent_id.slice(0,8)) + '</div>' +
|
||||
680→ '<div style="font-size: 12px; color: hsl(var(--muted-foreground));">' + statusText + ' • ' + started + '</div>' +
|
||||
681→ '</div>' +
|
||||
682→ '</div>' +
|
||||
683→ '</div>';
|
||||
684→ }).join("") + '</div>';
|
||||
685→ }
|
||||
686→
|
||||
687→ function selectMachine(id) {
|
||||
688→ selectedMachine = machines.find(m => m.id === id);
|
||||
@@ -0,0 +1 @@
|
||||
46841 /home/guru/guru-connect/target/release/guruconnect-server
|
||||
@@ -0,0 +1 @@
|
||||
Command running in background with ID: b5e79f0. Output is being written to: C:\Users\MIKESW~1\AppData\Local\Temp\claude\C--Users-MikeSwanson-Claude\tasks\b5e79f0.output
|
||||
@@ -0,0 +1,26 @@
|
||||
Updating 743b73d..3fc4e1f
|
||||
Fast-forward
|
||||
server/migrations/002_user_management.sql | 44 +++
|
||||
server/src/api/auth.rs | 317 ++++++++++++++++
|
||||
server/src/api/mod.rs | 3 +
|
||||
server/src/api/users.rs | 592 +++++++++++++++++++++++++++++
|
||||
server/src/auth/jwt.rs | 140 +++++++
|
||||
server/src/auth/mod.rs | 133 ++++++-
|
||||
server/src/auth/password.rs | 57 +++
|
||||
server/src/db/mod.rs | 2 +
|
||||
server/src/db/users.rs | 283 ++++++++++++++
|
||||
server/src/main.rs | 114 +++++-
|
||||
server/static/dashboard.html | 72 +++-
|
||||
server/static/login.html | 67 ++--
|
||||
server/static/users.html | 602 ++++++++++++++++++++++++++++++
|
||||
13 files changed, 2355 insertions(+), 71 deletions(-)
|
||||
create mode 100644 server/migrations/002_user_management.sql
|
||||
create mode 100644 server/src/api/auth.rs
|
||||
create mode 100644 server/src/api/users.rs
|
||||
create mode 100644 server/src/auth/jwt.rs
|
||||
create mode 100644 server/src/auth/password.rs
|
||||
create mode 100644 server/src/db/users.rs
|
||||
create mode 100644 server/static/users.html
|
||||
From https://git.azcomputerguru.com/azcomputerguru/guru-connect
|
||||
* branch main -> FETCH_HEAD
|
||||
743b73d..3fc4e1f main -> origin/main
|
||||
@@ -0,0 +1,4 @@
|
||||
remote: . Processing 1 references
|
||||
remote: Processed 1 references in total
|
||||
To https://git.azcomputerguru.com/azcomputerguru/guru-connect.git
|
||||
743b73d..3fc4e1f main -> main
|
||||
@@ -0,0 +1,62 @@
|
||||
280→ viewer_id: viewer_id.to_string(),
|
||||
281→ })),
|
||||
282→ };
|
||||
283→
|
||||
284→ let mut buf = Vec::new();
|
||||
285→ if stop_stream.encode(&mut buf).is_ok() {
|
||||
286→ let _ = session_data.input_tx.send(buf).await;
|
||||
287→ }
|
||||
288→ }
|
||||
289→
|
||||
290→ /// Remove a session (when agent disconnects)
|
||||
291→ pub async fn remove_session(&self, session_id: SessionId) {
|
||||
292→ let mut sessions = self.sessions.write().await;
|
||||
293→ if let Some(session_data) = sessions.remove(&session_id) {
|
||||
294→ let mut agents = self.agents.write().await;
|
||||
295→ agents.remove(&session_data.info.agent_id);
|
||||
296→ }
|
||||
297→ }
|
||||
298→
|
||||
299→ /// Disconnect a session by sending a disconnect message to the agent
|
||||
300→ /// Returns true if the message was sent successfully
|
||||
301→ pub async fn disconnect_session(&self, session_id: SessionId, reason: &str) -> bool {
|
||||
302→ let sessions = self.sessions.read().await;
|
||||
303→ if let Some(session_data) = sessions.get(&session_id) {
|
||||
304→ // Create disconnect message
|
||||
305→ use crate::proto;
|
||||
306→ use prost::Message;
|
||||
307→
|
||||
308→ let disconnect_msg = proto::Message {
|
||||
309→ payload: Some(proto::message::Payload::Disconnect(proto::Disconnect {
|
||||
310→ reason: reason.to_string(),
|
||||
311→ })),
|
||||
312→ };
|
||||
313→
|
||||
314→ let mut buf = Vec::new();
|
||||
315→ if disconnect_msg.encode(&mut buf).is_ok() {
|
||||
316→ // Send via input channel (will be forwarded to agent's WebSocket)
|
||||
317→ if session_data.input_tx.send(buf).await.is_ok() {
|
||||
318→ return true;
|
||||
319→ }
|
||||
320→ }
|
||||
321→ }
|
||||
322→ false
|
||||
323→ }
|
||||
324→
|
||||
325→ /// List all active sessions
|
||||
326→ pub async fn list_sessions(&self) -> Vec<Session> {
|
||||
327→ let sessions = self.sessions.read().await;
|
||||
328→ sessions.values().map(|s| s.info.clone()).collect()
|
||||
329→ }
|
||||
330→}
|
||||
331→
|
||||
332→impl Default for SessionManager {
|
||||
333→ fn default() -> Self {
|
||||
334→ Self::new()
|
||||
335→ }
|
||||
336→}
|
||||
337→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 1
|
||||
FATAL ERROR: Cannot answer interactive prompts in batch mode
|
||||
@@ -0,0 +1,16 @@
|
||||
1→[2m2025-12-30T04:02:27.326062Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m GuruConnect Server v0.1.0
|
||||
2→[2m2025-12-30T04:02:27.326153Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m Loaded configuration, listening on 0.0.0.0:3002
|
||||
3→[2m2025-12-30T04:02:27.326163Z[0m [33m WARN[0m [2mguruconnect_server[0m[2m:[0m JWT_SECRET not set, using default (INSECURE for production!)
|
||||
4→[2m2025-12-30T04:02:27.326169Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Connecting to database...
|
||||
5→[2m2025-12-30T04:02:27.341136Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Database connection established
|
||||
6→[2m2025-12-30T04:02:27.341174Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Running database migrations...
|
||||
7→[2m2025-12-30T04:02:27.346248Z[0m [32m INFO[0m [2msqlx::postgres::notice[0m[2m:[0m relation "_sqlx_migrations" already exists, skipping
|
||||
8→[2m2025-12-30T04:02:27.356332Z[0m [32m INFO[0m [2mguruconnect_server::db[0m[2m:[0m Migrations complete
|
||||
9→[2m2025-12-30T04:02:27.380581Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m 1 user(s) in database
|
||||
10→[2m2025-12-30T04:02:27.387175Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m Restoring 0 persistent machines from database
|
||||
11→[2m2025-12-30T04:02:27.388002Z[0m [32m INFO[0m [2mguruconnect_server[0m[2m:[0m Server listening on 0.0.0.0:3002
|
||||
12→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,2 @@
|
||||
/home/guru/.cargo/bin/cargo
|
||||
cargo 1.92.0 (344c4567c 2025-10-21)
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 255
|
||||
scp: stat local "C:/Users/MikeSwanson/Claude/guru-connect/target/release/guruconnect-server.exe": No such file or directory
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 127
|
||||
bash: line 1: cargo: command not found
|
||||
@@ -0,0 +1,2 @@
|
||||
sshpass not found, trying chocolatey install...
|
||||
/usr/bin/bash: line 1: choco: command not found
|
||||
@@ -0,0 +1 @@
|
||||
<system-reminder>Warning: the file exists but is shorter than the provided offset (1). The file has 1 lines.</system-reminder>
|
||||
@@ -0,0 +1,13 @@
|
||||
The file C:\Users\MikeSwanson\Claude\guru-connect\server\src\relay\mod.rs has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
|
||||
95→ return;
|
||||
96→ }
|
||||
97→ }
|
||||
98→
|
||||
99→ // Register the agent and get channels
|
||||
100→ // Persistent agents (no support code) keep their session when disconnected
|
||||
101→ let is_persistent = support_code.is_none();
|
||||
102→ let (session_id, frame_tx, mut input_rx) = sessions.register_agent(agent_id.clone(), agent_name.clone(), is_persistent).await;
|
||||
103→
|
||||
104→ info!("Session created: {} (agent in idle mode)", session_id);
|
||||
105→
|
||||
106→ // If a support code was provided, mark it as connected
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,71 @@
|
||||
f7708df Session log: Syncro/Autotask analysis, GlazTech firewall GPOs
|
||||
bb361ba Add GlazTech firewall GPO scripts and update credentials
|
||||
dd5ec53 Add Autotask API integration code
|
||||
7c01430 Add Autotask API credentials
|
||||
fcd85bc Clarify Syncro API belongs to AZ Computer Guru
|
||||
cfcc052 Add Syncro API key to credentials
|
||||
24c3fa6 Fix corrupted settings.local.json - remove merge conflict marker
|
||||
e3b32e5 Session log: MVAN 365 tenant consolidation project started
|
||||
9d2ee44 Session log: Mac sync, /s fix, MailProtector admin guide
|
||||
ebe7d7b Add MailProtector admin how-to guide
|
||||
4437d15 Fix /s command to include full save instructions
|
||||
0c75bfd Add UniversalDecryptor C# encryption projects
|
||||
e83944e Sync local Windows projects to repo
|
||||
18bb78a Move ACL exclusions back to msp-toolkit
|
||||
cf8f5bc Session 3: Grabb calendar fix - MySQL strict mode empty id handling
|
||||
58ce668 Session: MailProtector outbound filtering, per-project planning
|
||||
11fa313 Expand auto-save rule: include Claude-generated and discovered credentials
|
||||
e958ddd Add auto-save credentials rule to CLAUDE.md
|
||||
cf4d3d5 Session log: GuruRMM auto-update system complete, agent v0.3.4
|
||||
70cfea8 Merge remote changes, combine permissions from all machines
|
||||
2441326 Add SSH key status notes to config
|
||||
f9208a8 Add Mac SSH key and shared SSH config
|
||||
9a7c159 Add optimization pass notes for multi-machine setup
|
||||
dd175a6 Add GuruRMM build server credentials
|
||||
041ac0c Update session log: calendar display issues noted for future
|
||||
f51ff6f Session: Grabb & Durando calendar fix - PageSpeed was root cause
|
||||
c433fc5 Session: Grabb & Durando - event name field increase, user testing
|
||||
c0ce51d Session: Grabb & Durando calendar fix - missing INSERT execution
|
||||
60e6df1 Session: Grabb & Durando site fixes, user audit, credentials update
|
||||
842f301 Session log: GuruRMM agent service installers and network state
|
||||
c06b599 Windows agent builds successfully!
|
||||
1e60b36 Session save: Windows build setup, resume instructions
|
||||
1b572e8 Session update: GuruRMM agent development progress
|
||||
990c4dd Session log: Final save with GuruRMM roadmap additions
|
||||
c53de16 Add Dataforth project notes with time accounting
|
||||
8843219 Session log: Add GuruRMM feature roadmap summary
|
||||
6f88079 Update instructions with sent email changes
|
||||
833a08f Add datasheets share pending note - needs Engineering input
|
||||
5d19412 DOS test machines: TODO mechanism, staged COMMAND.COM update, docs
|
||||
9ef6206 Session log update: complete Dataforth DOS migration status
|
||||
141c705 Update email: clarify 13 years of slow boots will be fixed
|
||||
aaa93fd Dataforth DOS machines: complete documentation and update tools
|
||||
815a6dd Session log: Dataforth DOS machines - WORKING
|
||||
5a3a1d1 Session log: Dataforth DOS machines SMB1 proxy setup
|
||||
f762cc1 Session log: Jupiter performance fixes, GuruRMM rebuild
|
||||
8fa8d7c Session log: Windows data sync, credentials consolidation
|
||||
c01ff79 Sync Windows Claude data: credentials, seafile-graph-email, whm-dns-cleanup
|
||||
590a854 Add centralized credentials reference for cross-machine sync
|
||||
fd6a674 Fix: ensure folders created, add embedded credentials for sync
|
||||
6a9d72b Session log: GuruRMM SSL setup & context system improvement
|
||||
194426b Update global Claude Code permissions for all projects
|
||||
4f2d695 Add comprehensive global permissions for all machines
|
||||
5c0229d Move permissions exclusions to shared-data for cross-project use
|
||||
c979ecc Fix CRLF line ending issues when passing scripts to WSL bash
|
||||
e65af74 Session log: Claude Code permissions & settings sync
|
||||
27badda Fix: Ensure Gitea remote URL on existing repos
|
||||
902278c Add Claude Code settings sync across machines
|
||||
2e44e06 Quick session save: project listing
|
||||
873129a Session log: Mac local context sync
|
||||
bf14a2c Add Mac-local project contexts: Rust MSP toolkit and Claude Code setup
|
||||
4cd2e3e Make setup-claude-mac.sh executable
|
||||
152604d Update workstation setup: WSL-only mode + Mac support
|
||||
935f690 Session log: Claude Code workstation setup script
|
||||
6b19628 Add Claude Code workstation setup script for multi-machine deployment
|
||||
733f8f9 Session update: data.grabbanddurando.com SSL, DB, and data sync
|
||||
dd597d5 Session log update: WHM maintenance, security audit, DNS fix
|
||||
6354cd3 Add ralphstransfer.com security audit report
|
||||
b481c1f Add /s as quick alias for /save
|
||||
06ef1e3 Add /context and /sync commands for multi-machine workflow
|
||||
24fb9ed Add session logging system with /save command
|
||||
b13b1cb Add M365 security investigation scripts and Grabb website migration docs
|
||||
@@ -0,0 +1,10 @@
|
||||
9a52e57 Add Seafile Graph API email integration project
|
||||
893a7c5 v1.3.16: Add D7-style dashboard, logging improvements, bug fixes
|
||||
83badf9 Fix Write-Host capture in WPF GUI runspace
|
||||
79db916 v1.3.1: Add date/time check and fix update detection
|
||||
a5336cd Add status bar to WPF GUI
|
||||
1069b67 Fix WPF GUI script execution - use WPF Dispatcher instead of WinForms
|
||||
2d121c2 Fix Update-Toolkit.ps1 for web-based execution
|
||||
95e7045 v1.3.0: Add WPF GUI interface with console fallback
|
||||
9bcc07e v1.2.0: Get-SystemInfo.ps1 UX overhaul
|
||||
01c2631 MSP Toolkit v1.1.0 - Improved logging and error handling
|
||||
@@ -0,0 +1,2 @@
|
||||
Saved working directory and index state WIP on main: 9a52e57 Add Seafile Graph API email integration project
|
||||
warning: in the working copy of '.claude/settings.local.json', LF will be replaced by CRLF the next time Git touches it
|
||||
@@ -0,0 +1,11 @@
|
||||
.claude/commands/context.md
|
||||
.claude/commands/s.md
|
||||
.claude/commands/save.md
|
||||
.claude/commands/sync.md
|
||||
.claude/settings.local.json
|
||||
CLAUDE.md
|
||||
Website2025/CLAUDE.md
|
||||
claude-settings/settings.json
|
||||
msp-toolkit-rust/CLAUDE.md
|
||||
msp-toolkit-rust/src/config/settings.rs
|
||||
seafile-graph-email/seahub_settings_email.py.template
|
||||
@@ -0,0 +1,115 @@
|
||||
Exit code 2
|
||||
warning: in the working copy of '.claude/settings.local.json', LF will be replaced by CRLF the next time Git touches it
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B39DATA.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B39_2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B45DATA.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B49DATA.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B49_2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5BMAIN.106:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5BMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5BMAIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DB5B48.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT3.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT4.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT5.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/TE1012DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BLOG/TIMELOG/48-01.CSV:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/7BMAIN.106:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/7BMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/7BMAIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/TE1039DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BMAIN4.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BMAIN4.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/8B49DATA/8B49.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/8B49DATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/8BIOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/TEST49B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8B38X.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8B45X.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8B49.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OL2:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OL3:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OL4:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/DBSORT3.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/LIBATE.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TE1187DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TEST8B1A.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TEST8B1A.MAK:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TEST8B2A.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/5B39TEST.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive
|
||||
|
||||
... [61781 characters truncated] ...
|
||||
|
||||
s-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/8BLOG/34-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/8BLOG/35-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/8BLOG/39-04.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/8BLOG/45-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/8BLOG/45-04.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/8BLOG/45-05.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/30-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/31-12.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/31-12C.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/31-13C.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/31-15C.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/32-1572.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/34-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/34-03C.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/37K-02C.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/38-1762.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/40-06E.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/41-10.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/45-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/45-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/47J-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/DSCLOG/47K-04C.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/SCTLOG/31-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/SCTLOG/34-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/TS-11L/LOGS/VASLOG/VAS-M400.DAT:Zone.Identifier'
|
||||
error: Your local changes to the following files would be overwritten by merge:
|
||||
.claude/settings.local.json
|
||||
Please commit your changes or stash them before you merge.
|
||||
error: The following untracked working tree files would be overwritten by merge:
|
||||
whm-dns-cleanup/WHM-DNS-Cleanup-Report-2025-12-09.md
|
||||
whm-dns-cleanup/WHM-Recovery-Data-2025-12-09.md
|
||||
whm-dns-cleanup/whm-acme-records-removed.csv
|
||||
whm-dns-cleanup/whm-backup-status.csv
|
||||
whm-dns-cleanup/whm-check-backups.ps1
|
||||
whm-dns-cleanup/whm-check-expired-ns.ps1
|
||||
whm-dns-cleanup/whm-check-mx-spf.ps1
|
||||
whm-dns-cleanup/whm-check-ns.ps1
|
||||
whm-dns-cleanup/whm-cleanup-dnsonly.ps1
|
||||
whm-dns-cleanup/whm-comodo-records.csv
|
||||
whm-dns-cleanup/whm-configure-backups.ps1
|
||||
whm-dns-cleanup/whm-cpanel-records-to-remove.csv
|
||||
whm-dns-cleanup/whm-dns-analysis.ps1
|
||||
whm-dns-cleanup/whm-dns-debug.ps1
|
||||
whm-dns-cleanup/whm-dns-issues.csv
|
||||
whm-dns-cleanup/whm-dnsonly-deleted.csv
|
||||
whm-dns-cleanup/whm-dump-zones-before-delete.ps1
|
||||
whm-dns-cleanup/whm-fix-spf.ps1
|
||||
whm-dns-cleanup/whm-fix-spf2.ps1
|
||||
whm-dns-cleanup/whm-list-resellers.ps1
|
||||
whm-dns-cleanup/whm-mx-issues.csv
|
||||
whm-dns-cleanup/whm-relax-dmarc.ps1
|
||||
whm-dns-cleanup/whm-remove-acme-records.ps1
|
||||
whm-dns-cleanup/whm-remove-comodo.ps1
|
||||
whm-dns-cleanup/whm-remove-cpanel-records.ps1
|
||||
whm-dns-cleanup/whm-remove-dns-zones.ps1
|
||||
whm-dns-cleanup/whm-remove-expired-zones.ps1
|
||||
whm-dns-cleanup/whm-restore-autodiscover-a.ps1
|
||||
whm-dns-cleanup/whm-restore-autodiscover.ps1
|
||||
whm-dns-cleanup/whm-spf-issues.csv
|
||||
whm-dns-cleanup/whm-update-ns-records.ps1
|
||||
whm-dns-cleanup/whm-zone-backup-2025-12-09_155836/azsparklepoolservice.com.zone
|
||||
whm-dns-cleanup/whm-zone-backup-2025-12-09_155836/littleheartslittlehands.com.zone
|
||||
whm-dns-cleanup/whm-zones-lookup-failed.csv
|
||||
whm-dns-cleanup/whm-zones-not-using-acg-ns.csv
|
||||
whm-dns-cleanup/whm-zones-using-acg-ns.csv
|
||||
Please move or remove them before you merge.
|
||||
Aborting
|
||||
Merge with strategy ort failed.
|
||||
@@ -0,0 +1 @@
|
||||
No files found
|
||||
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"permissions": {
|
||||
"allow": [
|
||||
"Bash(git:*)",
|
||||
"Bash(gh:*)",
|
||||
"Bash(ssh:*)",
|
||||
"Bash(scp:*)",
|
||||
"Bash(rsync:*)",
|
||||
"Bash(wsl:*)",
|
||||
"Bash(wsl.exe:*)",
|
||||
"Bash(cat:*)",
|
||||
"Bash(ls:*)",
|
||||
"Bash(find:*)",
|
||||
"Bash(grep:*)",
|
||||
"Bash(echo:*)",
|
||||
"Bash(chmod:*)",
|
||||
"Bash(chown:*)",
|
||||
"Bash(mkdir:*)",
|
||||
"Bash(rm:*)",
|
||||
"Bash(cp:*)",
|
||||
"Bash(mv:*)",
|
||||
"Bash(curl:*)",
|
||||
"Bash(wget:*)",
|
||||
"Bash(nslookup:*)",
|
||||
"Bash(dig:*)",
|
||||
"Bash(ping:*)",
|
||||
"Bash(python:*)",
|
||||
"Bash(python3:*)",
|
||||
"Bash(node:*)",
|
||||
"Bash(npm:*)",
|
||||
"Bash(npx:*)",
|
||||
"Bash(cargo:*)",
|
||||
"Bash(rustc:*)",
|
||||
"Bash(powershell.exe:*)",
|
||||
"Bash(pwsh:*)",
|
||||
"Bash(which:*)",
|
||||
"Bash(where:*)",
|
||||
"Bash(whoami:*)",
|
||||
"Bash(date:*)",
|
||||
"Bash(head:*)",
|
||||
"Bash(tail:*)",
|
||||
"Bash(less:*)",
|
||||
"Bash(more:*)",
|
||||
"Bash(diff:*)",
|
||||
"Bash(tar:*)",
|
||||
"Bash(unzip:*)",
|
||||
"Bash(zip:*)",
|
||||
"Bash(docker:*)",
|
||||
"Bash(docker-compose:*)",
|
||||
"Bash(systemctl:*)",
|
||||
"Bash(service:*)",
|
||||
"Bash(journalctl:*)",
|
||||
"Bash(apt:*)",
|
||||
"Bash(apt-get:*)",
|
||||
"Bash(brew:*)",
|
||||
"Bash(code:*)",
|
||||
"Bash(make:*)",
|
||||
"Bash(cmake:*)",
|
||||
"WebFetch(domain:*)"
|
||||
],
|
||||
"deny": [],
|
||||
"ask": []
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
Dropped refs/stash@{0} (7c4ea1b18260de35e4effbfcb0659cc86eceeb0c)
|
||||
@@ -0,0 +1,4 @@
|
||||
gitea https://azcomputerguru:Window123!@#-git@git.azcomputerguru.com/azcomputerguru/claude-projects.git (fetch)
|
||||
gitea https://azcomputerguru:Window123!@#-git@git.azcomputerguru.com/azcomputerguru/claude-projects.git (push)
|
||||
origin https://github.com/AZComputerGuru/claude-projects.git (fetch)
|
||||
origin https://github.com/AZComputerGuru/claude-projects.git (push)
|
||||
@@ -0,0 +1,222 @@
|
||||
# CLAUDE.md
|
||||
|
||||
This file provides guidance to Claude Code (claude.ai/code) when working in any project under the claude-projects directory.
|
||||
|
||||
## AUTO-SAVE CREDENTIALS RULE
|
||||
|
||||
**CRITICAL: IMMEDIATELY save to `shared-data/credentials.md` whenever:**
|
||||
|
||||
1. **User provides** any credential (password, API key, token, etc.)
|
||||
2. **Claude generates** any credential (SSH keys, tokens, secrets, passwords, etc.)
|
||||
3. **Claude discovers** credentials during work (from config files, command output, etc.)
|
||||
|
||||
**Types of credentials to save:**
|
||||
- Passwords
|
||||
- API keys/tokens
|
||||
- SSH keys or passphrases
|
||||
- Database credentials
|
||||
- Service account credentials
|
||||
- OAuth tokens
|
||||
- JWT secrets
|
||||
- Generated secrets (random strings, hashes)
|
||||
- Any authentication information
|
||||
|
||||
**How to save:**
|
||||
1. Identify the service/system the credential belongs to
|
||||
2. Append to the appropriate section in `shared-data/credentials.md`
|
||||
3. Include: service name, username (if applicable), the credential, and any relevant URLs/hosts
|
||||
4. Confirm to the user that the credential was saved
|
||||
|
||||
**Format example:**
|
||||
```markdown
|
||||
### Service Name
|
||||
- **Host:** hostname or URL
|
||||
- **Username:** user
|
||||
- **Password/Token:** the_credential
|
||||
- **Notes:** any context (e.g., "generated 2025-12-16")
|
||||
```
|
||||
|
||||
**Do this IMMEDIATELY - do not wait until end of session. No credential should ever be lost.**
|
||||
|
||||
---
|
||||
|
||||
## Credentials & Auth Reference
|
||||
|
||||
**PRIMARY SOURCE:** `shared-data/credentials.md` - Consolidated credentials file for all services
|
||||
- SSH passwords for Jupiter, Saturn, pfSense
|
||||
- API tokens (Gitea, Cloudflare, NPM)
|
||||
- Database credentials (GuruRMM, client sites)
|
||||
- Service URLs and ports
|
||||
|
||||
**ALWAYS check this file first** when needing authentication info.
|
||||
|
||||
## Context Recovery
|
||||
|
||||
**When the user references previous work, conversations, or context, check these sources:**
|
||||
|
||||
1. **Credentials:** `shared-data/credentials.md` - All auth info consolidated here
|
||||
2. **Session logs:** `session-logs/` directory - Detailed work history
|
||||
3. **Search for context:** Use grep/search to find relevant keywords
|
||||
|
||||
**Information stored includes:**
|
||||
- Credentials and API keys (UNREDACTED for internal use)
|
||||
- Server/host information (IPs, ports, hostnames)
|
||||
- Configuration changes made
|
||||
- Important commands and their results
|
||||
- Decisions made in previous sessions
|
||||
- Unfinished/pending tasks
|
||||
|
||||
**Example usage:**
|
||||
- User says "use the cloudflare key from before" → Check `shared-data/credentials.md`
|
||||
- User says "connect to the server we set up" → Check credentials.md for SSH info
|
||||
- User says "continue where we left off" → Read most recent session log
|
||||
|
||||
## Infrastructure Reference
|
||||
|
||||
### SSH Hosts (from ~/.ssh/config)
|
||||
| Alias | Host | User | Port |
|
||||
|---|---|---|---|
|
||||
| pfsense | 172.16.0.1 | admin | 2248 |
|
||||
| jupiter | 172.16.3.20 | root | 22 |
|
||||
| saturn | 172.16.3.21 | root | 22 |
|
||||
|
||||
### Key Servers
|
||||
- **Jupiter** (172.16.3.20) - Unraid, primary container host (Gitea, NPM, GuruRMM)
|
||||
- **Saturn** (172.16.3.21) - Unraid, secondary/migration source
|
||||
- **pfSense** (172.16.0.1) - Firewall, Tailscale gateway
|
||||
|
||||
### Common Services
|
||||
- **Gitea:** https://git.azcomputerguru.com/ (internal: 172.16.3.20:3000)
|
||||
- **NPM:** Admin at 172.16.3.20:7818, HTTP/HTTPS on 1880/18443
|
||||
- **GuruRMM API:** https://rmm-api.azcomputerguru.com (172.16.3.20:3001)
|
||||
|
||||
## Slash Commands
|
||||
|
||||
### /save or /s
|
||||
Saves complete session context to `session-logs/YYYY-MM-DD-session.md`. Includes:
|
||||
- Complete summary of work done
|
||||
- ALL credentials, API keys, tokens (unredacted)
|
||||
- All server/host information
|
||||
- All commands run and their outputs
|
||||
- Decisions made and rationale
|
||||
- Errors encountered and resolutions
|
||||
- Pending/incomplete tasks
|
||||
|
||||
### /context
|
||||
Searches session logs for relevant context when user references previous work.
|
||||
|
||||
### /sync
|
||||
Commits and pushes changes to Gitea remote.
|
||||
|
||||
---
|
||||
|
||||
## Per-Project Planning
|
||||
|
||||
### Workflow
|
||||
1. **Active planning** uses `.claude/plans/` (system default)
|
||||
2. **On completion**, archive the plan to the project directory as `PLAN.md`
|
||||
3. **Future sessions** reference archived plans for context
|
||||
|
||||
### Archive Locations
|
||||
```
|
||||
/home/guru/claude-projects/gururmm/PLAN.md
|
||||
/home/guru/claude-projects/mailprotector-config/PLAN.md
|
||||
/home/guru/claude-projects/dataforth/PLAN.md
|
||||
/home/guru/claude-projects/[project]/PLAN.md
|
||||
```
|
||||
|
||||
### When to Archive
|
||||
Archive the active plan when:
|
||||
- Implementation is complete
|
||||
- Switching to a different project
|
||||
- Plan is approved but work will continue later
|
||||
- Session is ending with incomplete work
|
||||
|
||||
### Archive Command
|
||||
After exiting plan mode, run:
|
||||
```bash
|
||||
cp /home/guru/.claude/plans/*.md /home/guru/claude-projects/[project]/PLAN.md
|
||||
```
|
||||
|
||||
Or Claude will do this automatically when:
|
||||
- User says "archive the plan" or "save the plan"
|
||||
- A project switch is detected
|
||||
- Session save (/save) is triggered
|
||||
|
||||
### Plan File Contents
|
||||
Archived plans should include:
|
||||
- Overview and goals
|
||||
- Implementation steps (completed and pending)
|
||||
- Key decisions made
|
||||
- Files modified
|
||||
- Testing notes
|
||||
- Next steps (if incomplete)
|
||||
|
||||
### Recovering Context
|
||||
When resuming a project:
|
||||
1. Check for `PLAN.md` in the project directory
|
||||
2. Read to understand previous work and pending tasks
|
||||
3. Continue from where left off or start new plan if needed
|
||||
|
||||
---
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always save context frequently** - Use /save before long operations or context limits
|
||||
2. **Check existing context first** - Before asking user for info, check session logs
|
||||
3. **Store ALL credentials** - Session logs are the source of truth for credentials
|
||||
4. **Track infrastructure changes** - Document any server/config changes
|
||||
5. **Note pending tasks** - Future sessions should know what's incomplete
|
||||
|
||||
## Projects
|
||||
|
||||
This repository contains session logs and shared configuration for multiple projects including:
|
||||
- GuruRMM (custom RMM system)
|
||||
- MSP Toolkit (integrated CLI for MSP operations)
|
||||
- Website2025 (web development projects)
|
||||
- Various infrastructure automation
|
||||
|
||||
Each project may have its own CLAUDE.md with project-specific guidance.
|
||||
|
||||
---
|
||||
|
||||
## OPTIMIZATION PASS REQUIRED
|
||||
|
||||
**Status:** Pending
|
||||
**Priority:** High
|
||||
**Requested:** 2025-12-16
|
||||
|
||||
### Tasks for All Machines
|
||||
|
||||
When starting a session, perform these optimization tasks:
|
||||
|
||||
#### 1. Repository Cleanup
|
||||
- [ ] Ensure all git remotes point to Gitea (`git.azcomputerguru.com`), not GitHub
|
||||
- [ ] Remove any stale GitHub references
|
||||
- [ ] Verify repos are properly synced with Gitea
|
||||
|
||||
#### 2. Credential Consolidation
|
||||
- [ ] All credentials should be in `shared-data/credentials.md`
|
||||
- [ ] Git credentials accessible via API token (see credentials.md)
|
||||
- [ ] SSH keys consistent across machines
|
||||
|
||||
#### 3. Project Organization
|
||||
- [ ] Each project has proper CLAUDE.md
|
||||
- [ ] Session logs are up to date
|
||||
- [ ] No orphaned files or configurations
|
||||
|
||||
#### 4. Machine-Specific Verification
|
||||
| Machine | Tasks |
|
||||
|---------|-------|
|
||||
| Mac (ACG-M-L5090) | Verify osxkeychain has Gitea creds, SSH keys working |
|
||||
| Windows/WSL | Verify git-credential-store, SSH keys in ~/.ssh |
|
||||
| Ubuntu Server (172.16.3.30) | Verify Gitea clone access, build tools working |
|
||||
|
||||
### Current Gitea Repositories
|
||||
- `azcomputerguru/gururmm` - RMM system (Rust server, React dashboard, Rust agent)
|
||||
- `azcomputerguru/claude-projects` - Claude Code workspace, session logs, shared data
|
||||
|
||||
### Pending Work (from gururmm)
|
||||
- Build and deploy updated agent with extended metrics (uptime, public IP, idle time)
|
||||
- Agent source is updated but needs to be built on Ubuntu server (172.16.3.30)
|
||||
- Server and dashboard already deployed with extended metrics support
|
||||
@@ -0,0 +1,2 @@
|
||||
warning: in the working copy of 'whm-dns-cleanup/whm-zone-backup-2025-12-09_155836/azsparklepoolservice.com.zone', LF will be replaced by CRLF the next time Git touches it
|
||||
warning: in the working copy of 'whm-dns-cleanup/whm-zone-backup-2025-12-09_155836/littleheartslittlehands.com.zone', LF will be replaced by CRLF the next time Git touches it
|
||||
@@ -0,0 +1,42 @@
|
||||
[main 0d9f741] Add local Windows changes before Gitea sync
|
||||
40 files changed, 7004 insertions(+)
|
||||
create mode 100644 msp-toolkit/BUILD-EXE.md
|
||||
create mode 100644 msp-toolkit/build-bundled.ps1
|
||||
create mode 100644 msp-toolkit/build-exe.ps1
|
||||
create mode 100644 msp-toolkit/msp-fix.ps1
|
||||
create mode 100644 whm-dns-cleanup/WHM-DNS-Cleanup-Report-2025-12-09.md
|
||||
create mode 100644 whm-dns-cleanup/WHM-Recovery-Data-2025-12-09.md
|
||||
create mode 100644 whm-dns-cleanup/whm-acme-records-removed.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-backup-status.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-check-backups.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-check-expired-ns.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-check-mx-spf.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-check-ns.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-cleanup-dnsonly.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-comodo-records.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-configure-backups.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-cpanel-records-to-remove.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-dns-analysis.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-dns-debug.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-dns-issues.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-dnsonly-deleted.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-dump-zones-before-delete.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-fix-spf.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-fix-spf2.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-list-resellers.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-mx-issues.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-relax-dmarc.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-remove-acme-records.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-remove-comodo.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-remove-cpanel-records.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-remove-dns-zones.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-remove-expired-zones.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-restore-autodiscover-a.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-restore-autodiscover.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-spf-issues.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-update-ns-records.ps1
|
||||
create mode 100644 whm-dns-cleanup/whm-zone-backup-2025-12-09_155836/azsparklepoolservice.com.zone
|
||||
create mode 100644 whm-dns-cleanup/whm-zone-backup-2025-12-09_155836/littleheartslittlehands.com.zone
|
||||
create mode 100644 whm-dns-cleanup/whm-zones-lookup-failed.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-zones-not-using-acg-ns.csv
|
||||
create mode 100644 whm-dns-cleanup/whm-zones-using-acg-ns.csv
|
||||
@@ -0,0 +1 @@
|
||||
Exit: 0
|
||||
@@ -0,0 +1 @@
|
||||
Exit: 0
|
||||
@@ -0,0 +1,24 @@
|
||||
Exit code 1
|
||||
error: Your local changes to the following files would be overwritten by merge:
|
||||
.claude/settings.local.json
|
||||
Please commit your changes or stash them before you merge.
|
||||
Aborting
|
||||
|
||||
On branch main
|
||||
Your branch is up to date with 'origin/main'.
|
||||
|
||||
Changes not staged for commit:
|
||||
(use "git add <file>..." to update what will be committed)
|
||||
(use "git restore <file>..." to discard changes in working directory)
|
||||
modified: .claude/settings.local.json
|
||||
|
||||
Untracked files:
|
||||
(use "git add <file>..." to include in what will be committed)
|
||||
msp-toolkit/BUILD-EXE.md
|
||||
msp-toolkit/build-bundled.ps1
|
||||
msp-toolkit/build-exe.ps1
|
||||
msp-toolkit/msp-fix.ps1
|
||||
whm-dns-cleanup/
|
||||
|
||||
no changes added to commit (use "git add" and/or "git commit -a")
|
||||
The stash entry is kept in case you need it again.
|
||||
@@ -0,0 +1,263 @@
|
||||
1→# Credentials & Authorization Reference
|
||||
2→**Last Updated:** 2025-12-16
|
||||
3→**Purpose:** Centralized credentials for Claude Code context recovery across all machines
|
||||
4→
|
||||
5→---
|
||||
6→
|
||||
7→## Infrastructure - SSH Access
|
||||
8→
|
||||
9→### Jupiter (Unraid Primary)
|
||||
10→- **Host:** 172.16.3.20
|
||||
11→- **User:** root
|
||||
12→- **Port:** 22
|
||||
13→- **Password:** Th1nk3r^99##
|
||||
14→- **Role:** Primary container host (Gitea, NPM, GuruRMM, media)
|
||||
15→
|
||||
16→### Saturn (Unraid Secondary)
|
||||
17→- **Host:** 172.16.3.21
|
||||
18→- **User:** root
|
||||
19→- **Port:** 22
|
||||
20→- **Password:** r3tr0gradE99
|
||||
21→- **Role:** Migration source, being consolidated to Jupiter
|
||||
22→
|
||||
23→### pfSense (Firewall)
|
||||
24→- **Host:** 172.16.0.1
|
||||
25→- **User:** admin
|
||||
26→- **Port:** 2248
|
||||
27→- **Role:** Firewall, Tailscale gateway
|
||||
28→- **Tailscale IP:** 100.127.188.119
|
||||
29→
|
||||
30→### OwnCloud VM (on Jupiter)
|
||||
31→- **Host:** 172.16.3.22
|
||||
32→- **Hostname:** cloud.acghosting.com
|
||||
33→- **User:** root
|
||||
34→- **Port:** 22
|
||||
35→- **Password:** r3tr0gradE99!!
|
||||
36→- **OS:** Rocky Linux 9.6
|
||||
37→- **Role:** OwnCloud file sync server
|
||||
38→- **Services:** Apache, MariaDB, PHP-FPM, Redis, Datto RMM agents
|
||||
39→- **Storage:** SMB mount from Jupiter (/mnt/user/OwnCloud)
|
||||
40→- **Note:** Jupiter has SSH key auth configured
|
||||
41→
|
||||
42→### GuruRMM Build Server
|
||||
43→- **Host:** 172.16.3.30
|
||||
44→- **Hostname:** gururmm
|
||||
45→- **User:** guru
|
||||
46→- **Port:** 22
|
||||
47→- **Password:** Gptf*77ttb!@#!@#-rmm
|
||||
48→- **OS:** Ubuntu 22.04
|
||||
49→- **Role:** GuruRMM dedicated server (API, DB, Dashboard, Downloads)
|
||||
50→- **Services:** nginx, PostgreSQL, gururmm-server, gururmm-agent
|
||||
51→- **Note:** WSL has SSH key auth configured
|
||||
52→
|
||||
53→---
|
||||
54→
|
||||
55→## Services - Web Applications
|
||||
56→
|
||||
57→### Gitea (Git Server)
|
||||
58→- **URL:** https://git.azcomputerguru.com/
|
||||
59→- **Internal:** http://172.16.3.20:3000
|
||||
60→- **SSH:** ssh://git@172.16.3.20:2222
|
||||
61→- **User:** mike@azcomputerguru.com
|
||||
62→- **Password:** Window123!@#-git
|
||||
63→- **API Token:** 9b1da4b79a38ef782268341d25a4b6880572063f
|
||||
64→
|
||||
65→### NPM (Nginx Proxy Manager)
|
||||
66→- **Admin URL:** http://172.16.3.20:7818
|
||||
67→- **HTTP Port:** 1880
|
||||
68→- **HTTPS Port:** 18443
|
||||
69→- **User:** mike@azcomputerguru.com
|
||||
70→- **Password:** r3tr0gradE99!
|
||||
71→
|
||||
72→### Cloudflare
|
||||
73→- **API Token:** U1UTbBOWA4a69eWEBiqIbYh0etCGzrpTU4XaKp7w
|
||||
74→- **Used for:** DNS challenges (Let's Encrypt), DNS management
|
||||
75→- **Domain:** azcomputerguru.com
|
||||
76→
|
||||
77→---
|
||||
78→
|
||||
79→## Projects - GuruRMM
|
||||
80→
|
||||
81→### Database (PostgreSQL)
|
||||
82→- **Host:** gururmm-db container (172.16.3.20)
|
||||
83→- **Database:** gururmm
|
||||
84→- **User:** gururmm
|
||||
85→- **Password:** 43617ebf7eb242e814ca9988cc4df5ad
|
||||
86→
|
||||
87→### API Server
|
||||
88→- **External URL:** https://rmm-api.azcomputerguru.com
|
||||
89→- **Internal URL:** http://172.16.3.20:3001
|
||||
90→- **JWT Secret:** ZNzGxghru2XUdBVlaf2G2L1YUBVcl5xH0lr/Gpf/QmE=
|
||||
91→
|
||||
92→### Containers on Jupiter
|
||||
93→- `gururmm-server` - API + WebSocket (port 3001)
|
||||
94→- `gururmm-db` - PostgreSQL 16
|
||||
95→
|
||||
96→---
|
||||
97→
|
||||
98→## Client Sites - WHM/cPanel
|
||||
99→
|
||||
100→### IX Server (ix.azcomputerguru.com)
|
||||
101→- **SSH Host:** ix.azcomputerguru.com
|
||||
102→- **Internal IP:** 172.16.3.10 (VPN required)
|
||||
103→- **SSH User:** root
|
||||
104→- **SSH Password:** Gptf*77ttb!@#!@#
|
||||
105→- **SSH Key:** guru@wsl key added to authorized_keys
|
||||
106→- **Role:** cPanel/WHM server hosting client sites
|
||||
107→
|
||||
108→### WebSvr (websvr.acghosting.com)
|
||||
109→- **Host:** websvr.acghosting.com
|
||||
110→- **SSH User:** root
|
||||
111→- **SSH Password:** r3tr0gradE99#
|
||||
112→- **API Token:** 8ZPYVM6R0RGOHII7EFF533MX6EQ17M7O
|
||||
113→- **Access Level:** Full access
|
||||
114→- **Role:** Legacy cPanel/WHM server (migration source to IX)
|
||||
115→
|
||||
116→### data.grabbanddurando.com
|
||||
117→- **Server:** IX (ix.azcomputerguru.com)
|
||||
118→- **cPanel Account:** grabblaw
|
||||
119→- **Site Path:** /home/grabblaw/public_html/data_grabbanddurando
|
||||
120→- **Site Admin User:** admin
|
||||
121→- **Site Admin Password:** GND-Paper123!@#-datasite
|
||||
122→- **Database:** grabblaw_gdapp_data
|
||||
123→- **DB User:** grabblaw_gddata
|
||||
124→- **DB Password:** GrabbData2025
|
||||
125→- **Config File:** /home/grabblaw/public_html/data_grabbanddurando/connection.php
|
||||
126→- **Backups:** /home/grabblaw/public_html/data_grabbanddurando/backups_mariadb_fix/
|
||||
127→
|
||||
128→### GoDaddy VPS (Legacy)
|
||||
129→- **IP:** 208.109.235.224
|
||||
130→- **Hostname:** 224.235.109.208.host.secureserver.net
|
||||
131→- **Auth:** SSH key
|
||||
132→- **Database:** grabblaw_gdapp
|
||||
133→- **Note:** Old server, data migrated to IX
|
||||
134→
|
||||
135→---
|
||||
136→
|
||||
137→## Seafile (on Saturn)
|
||||
138→
|
||||
139→### Container
|
||||
140→- **Host:** Saturn (172.16.3.21)
|
||||
141→- **URL:** https://sync.azcomputerguru.com
|
||||
142→- **Container:** seafile
|
||||
143→
|
||||
144→### Database (MySQL)
|
||||
145→- **Container:** seafile-mysql
|
||||
146→- **Root Password:** db_dev
|
||||
147→- **Seafile User:** seafile
|
||||
148→- **Seafile Password:** 64f2db5e-6831-48ed-a243-d4066fe428f9
|
||||
149→- **Database:** ccnet_db (users), seafile_db (data), seahub_db (web)
|
||||
150→
|
||||
151→### Microsoft Graph API (Email)
|
||||
152→- **Tenant ID:** ce61461e-81a0-4c84-bb4a-7b354a9a356d
|
||||
153→- **Client ID:** 15b0fafb-ab51-4cc9-adc7-f6334c805c22
|
||||
154→- **Client Secret:** rRN8Q~FPfSL8O24iZthi_LVJTjGOCZG.DnxGHaSk
|
||||
155→- **Sender Email:** noreply@azcomputerguru.com
|
||||
156→- **Used for:** Seafile email notifications via Graph API
|
||||
157→
|
||||
158→---
|
||||
159→
|
||||
160→## NPM Proxy Hosts Reference
|
||||
161→
|
||||
162→| ID | Domain | Backend | SSL Cert |
|
||||
163→|----|--------|---------|----------|
|
||||
164→| 1 | emby.azcomputerguru.com | 172.16.2.99:8096 | npm-1 |
|
||||
165→| 2 | git.azcomputerguru.com | 172.16.3.20:3000 | npm-2 |
|
||||
166→| 4 | plexrequest.azcomputerguru.com | 172.16.3.31:5055 | npm-4 |
|
||||
167→| 5 | rmm-api.azcomputerguru.com | 172.16.3.20:3001 | npm-6 |
|
||||
168→| - | unifi.azcomputerguru.com | 172.16.3.28:8443 | npm-5 |
|
||||
169→
|
||||
170→---
|
||||
171→
|
||||
172→## Tailscale Network
|
||||
173→
|
||||
174→| Tailscale IP | Hostname | Owner | OS |
|
||||
175→|--------------|----------|-------|-----|
|
||||
176→| 100.127.188.119 | pfsense | mike@ | freebsd |
|
||||
177→| 100.125.36.6 | acg-m-l5090 | mike@ | windows |
|
||||
178→| 100.92.230.111 | acg-tech-01l | mike@ | windows |
|
||||
179→| 100.96.135.117 | acg-tech-02l | mike@ | windows |
|
||||
180→| 100.113.45.7 | acg-tech03l | howard@ | windows |
|
||||
181→| 100.77.166.22 | desktop-hjfjtep | mike@ | windows |
|
||||
182→| 100.101.145.100 | guru-legion9 | mike@ | windows |
|
||||
183→| 100.119.194.51 | guru-surface8 | howard@ | windows |
|
||||
184→| 100.66.103.110 | magus-desktop | rob@ | windows |
|
||||
185→| 100.66.167.120 | magus-pc | rob@ | windows |
|
||||
186→
|
||||
187→---
|
||||
188→
|
||||
189→## SSH Public Keys
|
||||
190→
|
||||
191→### guru@wsl (Windows/WSL)
|
||||
192→- **User:** guru
|
||||
193→- **Sudo Password:** Window123!@#-wsl
|
||||
194→- **SSH Key:**
|
||||
195→```
|
||||
196→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAWY+SdqMHJP5JOe3qpWENQZhXJA4tzI2d7ZVNAwA/1u guru@wsl
|
||||
197→```
|
||||
198→
|
||||
199→### azcomputerguru@local (Mac)
|
||||
200→- **User:** azcomputerguru
|
||||
201→- **SSH Key:**
|
||||
202→```
|
||||
203→ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIDrGbr4EwvQ4P3ZtyZW3ZKkuDQOMbqyAQUul2+JE4K4S azcomputerguru@local
|
||||
204→```
|
||||
205→
|
||||
206→---
|
||||
207→
|
||||
208→## Quick Reference Commands
|
||||
209→
|
||||
210→### NPM API Auth
|
||||
211→```bash
|
||||
212→curl -s -X POST http://172.16.3.20:7818/api/tokens \
|
||||
213→ -H "Content-Type: application/json" \
|
||||
214→ -d '{"identity":"mike@azcomputerguru.com","secret":"r3tr0gradE99!"}'
|
||||
215→```
|
||||
216→
|
||||
217→### Gitea API
|
||||
218→```bash
|
||||
219→curl -H "Authorization: token 9b1da4b79a38ef782268341d25a4b6880572063f" \
|
||||
220→ https://git.azcomputerguru.com/api/v1/repos/search
|
||||
221→```
|
||||
222→
|
||||
223→### GuruRMM Health Check
|
||||
224→```bash
|
||||
225→curl http://172.16.3.20:3001/health
|
||||
226→```
|
||||
227→
|
||||
228→---
|
||||
229→
|
||||
230→## MSP Tools
|
||||
231→
|
||||
232→### Syncro (PSA/RMM) - AZ Computer Guru
|
||||
233→- **API Key:** T259810e5c9917386b-52c2aeea7cdb5ff41c6685a73cebbeb3
|
||||
234→- **Subdomain:** computerguru
|
||||
235→- **API Base URL:** https://computerguru.syncromsp.com/api/v1
|
||||
236→- **API Docs:** https://api-docs.syncromsp.com/
|
||||
237→- **Account:** AZ Computer Guru MSP
|
||||
238→- **Notes:** Added 2025-12-18
|
||||
239→
|
||||
240→### Autotask (PSA) - AZ Computer Guru
|
||||
241→- **API Username:** dguyqap2nucge6r@azcomputerguru.com
|
||||
242→- **API Password:** z*6G4fT#oM~8@9Hxy$2Y7K$ma
|
||||
243→- **API Integration Code:** HYTYYZ6LA5HB5XK7IGNA7OAHQLH
|
||||
244→- **Integration Name:** ClaudeAPI
|
||||
245→- **API Zone:** webservices5.autotask.net
|
||||
246→- **API Docs:** https://autotask.net/help/developerhelp/Content/APIs/REST/REST_API_Home.htm
|
||||
247→- **Account:** AZ Computer Guru MSP
|
||||
248→- **Notes:** Added 2025-12-18, new API user "Claude API"
|
||||
249→
|
||||
250→---
|
||||
251→
|
||||
252→## Client - MVAN Inc
|
||||
253→
|
||||
254→### Microsoft 365 Tenant 1
|
||||
255→- **Tenant:** mvan.onmicrosoft.com
|
||||
256→- **Admin User:** sysadmin@mvaninc.com
|
||||
257→- **Password:** r3tr0gradE99#
|
||||
258→- **Notes:** Global admin, project to merge/trust with T2
|
||||
259→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
@@ -0,0 +1,4 @@
|
||||
Exit code 128
|
||||
fatal: ambiguous argument 'gitea\main;.claude\settings.local.json': unknown revision or path not in the working tree.
|
||||
Use '--' to separate paths from revisions, like this:
|
||||
'git <command> [<revision>...] -- [<file>...]'
|
||||
@@ -0,0 +1 @@
|
||||
File not found on gitea/main
|
||||
@@ -0,0 +1 @@
|
||||
C:\Users\MikeSwanson\Claude\Website2025\CLAUDE.md
|
||||
@@ -0,0 +1,2 @@
|
||||
Exit code 128
|
||||
fatal: unable to access 'https://#-git@git.azcomputerguru.com/azcomputerguru/claude-projects.git/': URL rejected: No host part in the URL
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
||||
C:\Users\MikeSwanson\Claude\.claude\settings.local.json
|
||||
@@ -0,0 +1,266 @@
|
||||
warning: in the working copy of '.claude/settings.local.json', LF will be replaced by CRLF the next time Git touches it
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B39DATA.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B39_2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B45DATA.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B49DATA.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5B49_2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5BMAIN.106:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5BMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/5BMAIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DB5B48.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT3.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT4.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/DBSORT5.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BDATA/TE1012DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/5BLOG/TIMELOG/48-01.CSV:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/7BMAIN.106:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/7BMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/7BMAIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BDATA/TE1039DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BMAIN4.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/7BMAIN4.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/8B49DATA/8B49.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/8B49DATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/8BIOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8B49/TEST49B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8B38X.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8B45X.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8B49.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OL2:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OL3:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OL4:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/8BMAIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/DBSORT3.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/LIBATE.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TE1187DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TEST8B1A.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TEST8B1A.MAK:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/8BDATA/TEST8B2A.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/5B39TEST.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/5B45TEST.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/5B45TEST.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/5B49TEST.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/7BXXTEST.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/8B38TEMP.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/8BFIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/8BIOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/8BPWR.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/8BRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/8BVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/ABC5BRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/ABC5BVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/DSCFIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/DSCIOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/DSCRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/DSCVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/DSCVOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/K8BRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/K8BVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KDSCFIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KDSCIOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KDSCRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KDSCVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KDSCVOUT.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KRMSIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KSCTRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KSCTVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/KVAS.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/RMSIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/SCM5B48.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/SCM5BRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/SCM5BVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/SCTRIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/SCTVIN.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/ADDR/VAS.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/BC.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/BRUN45.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DFT7ANAL.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DBSORT4.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCFIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCFIN.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCMAIN4.106:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCMAIN4.317:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCMAIN4.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCOUT.206:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCOUT.317:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/DSCOUT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/DSCDATA/TE1053DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/GPIBOK4.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/GPIBOK5.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/HISTORY/DSCFIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/HISTORY/TDSCFIN.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/KDSCFIN.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/KDSCOUT1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/LIB.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/LIB5BX9.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/LIBATE.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/LIBATED.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/LINK.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/LOGPATH.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENU.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENU.OL1:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENU.OL2:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENU.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENU.ROY:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENUCA.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENUCA.ROY:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENUCB.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENUX.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/MENUX.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/30-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/30-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/30-03.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/31-03.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/31-03D.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/32-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/34-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/35-01D.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/36-04.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/37J-1488.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/38-02.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/40-03.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/41-02D.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/41-09.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/41-1182.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/42-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/45-04.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/45-08.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/45-25D.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/47K-05.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PRE0831/48-01.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWR/8BPWR.ADR:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWR/PWRDATA/8BMAIN2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWR/PWRDATA/8BPWR.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWR/PWRDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWR/TESTPWR.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWR/TESTPWR.MAK:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWRDATA/8BPWR.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/PWRDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/QB.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/RMSDATA:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/RMSMN4-1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/RMSMN5-1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/RMSMN6-1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/SCTDATA/DBSORT2.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/SCTDATA/SCTMAIN.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/SCTDATA/TE1035DT.DAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/SETADDR.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TDSCFIN.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TDSCOUT1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST39.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST45.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST49.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST49.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST49.MAK:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST49B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST5B1A.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST5B1B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST5B1E.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.12:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.MAK:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.OL2:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.OL3:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1A.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1C.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1C.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1D.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1D.OL2:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B1D.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B2A.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B39.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B39.OL2:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B39.OLD:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TEST8B45.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TESTHV1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TESTHV3.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TESTPW17.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TO5B1E.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TS5B48.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST2DIN1.C05:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST2DIN1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST4SCT1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST5B45B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST5B481.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST5B49B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST5SCT1.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST8B381.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TST8B49D.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTDIN1B.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTDIN1B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTDIN1B.MAK:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTDIN2B.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTS8B38.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTSHT49.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTSHT5B.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTSTD45.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTSTDSC.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATE/TSTSTRMS.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATESOLU/CBTEST.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATESOLU/CHSEL.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATESOLU/DACTEST.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/ATESOLU/MUXTEST.BAS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/AUTOEXEC.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/ANALYZE.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/ATE.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/CTOT16.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/CTOTTXT.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/CTOXTXT.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/M.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/MENU.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/BAT/TTOC16.BAT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/CDPRO/README:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/CDPRO/VIDE-CDD.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/COMMAND.COM:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/CONFIG.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/ANSI.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/APPEND.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/ATTRIB.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/AUTOEXEC.UMB:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/CHKDSK.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/CHKLIST.MS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/CHKSTATE.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/CHOICE.COM:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/CONFIG.UMB:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/COUNTRY.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/COUNTRY.TXT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DBLWIN.HLP:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DEBUG.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DEFAULT.SET:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DEFAULT.SLT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DEFRAG.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DEFRAG.HLP:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DELTREE.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DISKCOMP.COM:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DISKCOPY.COM:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DISPLAY.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DOSHELP.HLP:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DOSKEY.COM:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DOSSETUP.INI:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRIVER.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRVSPACE.BIN:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRVSPACE.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRVSPACE.HLP:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRVSPACE.INF:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRVSPACE.SYS:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/DRVSPACE.TXT:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EDIT.COM:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EDIT.HLP:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EGA.CPI:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EGA2.CPI:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EGA3.CPI:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EMM386.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/EXPAND.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/FASTHELP.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/FASTOPEN.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/FC.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/FDISK.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/FIND.EXE:Zone.Identifier'
|
||||
error: invalid path 'dataforth/dos-photos/Drive Image of unknown Test Machine/DOS/FORMAT.COM:Zone.I
|
||||
|
||||
... [354 lines truncated] ...
|
||||
@@ -0,0 +1,41 @@
|
||||
1→{
|
||||
2→ "permissions": {
|
||||
3→ "allow": [
|
||||
4→ "Bash(git --version:*)",
|
||||
5→ "Bash(git config:*)",
|
||||
6→ "Bash(git init:*)",
|
||||
7→ "Bash(gh --version:*)",
|
||||
8→ "Bash(winget install:*)",
|
||||
9→ "Bash(\"C:\\Program Files\\GitHub CLI\\gh.exe\" auth status)",
|
||||
10→ "Bash(\"C:\\Program Files\\GitHub CLI\\gh.exe\" repo create claude-projects --private --source=. --remote=origin --description \"Claude Code projects workspace\")",
|
||||
11→ "Bash(git add:*)",
|
||||
12→ "Bash(git commit:*)",
|
||||
13→ "Bash(git branch:*)",
|
||||
14→ "Bash(git push:*)",
|
||||
15→ "Bash(git pull:*)",
|
||||
16→ "Bash(\"C:\\Program Files\\GitHub CLI\\gh.exe\" repo view AZComputerGuru/claude-projects --json name,description,pushedAt)",
|
||||
17→ "Bash(\"C:\\Program Files\\GitHub CLI\\gh.exe\" api repos/AZComputerGuru/claude-projects/contents --jq \".[].name\")",
|
||||
18→ "WebFetch(domain:dev.computerguru.me)",
|
||||
19→ "Bash(cat:*)",
|
||||
20→ "Bash(ssh:*)",
|
||||
21→ "Bash(export GIT_SSH_COMMAND=\"C:/Windows/System32/OpenSSH/ssh.exe\")",
|
||||
22→ "Bash(/c/Windows/System32/OpenSSH/ssh.exe:*)",
|
||||
23→ "Bash(GIT_SSH_COMMAND=\"ssh -i ~/.ssh/id_ed25519 -o IdentitiesOnly=yes -o BatchMode=yes\" ssh root@ix.azcomputerguru.com \"echo ''BatchMode test''\")",
|
||||
24→ "Bash(echo:*)",
|
||||
25→ "Bash(/c/Windows/System32/OpenSSH/ssh-add.exe -l)",
|
||||
26→ "Bash(chmod:*)",
|
||||
27→ "WebFetch(domain:www.samsung.com)",
|
||||
28→ "Bash(scp:*)",
|
||||
29→ "Bash(curl:*)",
|
||||
30→ "Bash(git fetch:*)",
|
||||
31→ "Bash(git remote set-url:*)"
|
||||
32→ ],
|
||||
33→ "deny": [],
|
||||
34→ "ask": []
|
||||
35→ }
|
||||
36→}
|
||||
37→
|
||||
|
||||
<system-reminder>
|
||||
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
||||
</system-reminder>
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user