Files
claudetools/projects/msp-tools/guru-rmm/server/src/db/commands.rs
Mike Swanson a602118f6d Add VPN configuration tools and agent documentation
Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection
and enhanced agent documentation framework.

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

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

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

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

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

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

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-18 11:51:47 -07:00

164 lines
3.9 KiB
Rust

//! Commands database operations
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use sqlx::PgPool;
use uuid::Uuid;
/// Command record from database
#[derive(Debug, Clone, Serialize, Deserialize, sqlx::FromRow)]
pub struct Command {
pub id: Uuid,
pub agent_id: Uuid,
pub command_type: String,
pub command_text: String,
pub status: String,
pub exit_code: Option<i32>,
pub stdout: Option<String>,
pub stderr: Option<String>,
pub created_at: DateTime<Utc>,
pub started_at: Option<DateTime<Utc>>,
pub completed_at: Option<DateTime<Utc>>,
pub created_by: Option<Uuid>,
}
/// Create a new command
#[derive(Debug, Clone, Deserialize)]
pub struct CreateCommand {
pub agent_id: Uuid,
pub command_type: String,
pub command_text: String,
pub created_by: Option<Uuid>,
}
/// Insert a new command
pub async fn create_command(pool: &PgPool, cmd: CreateCommand) -> Result<Command, sqlx::Error> {
sqlx::query_as::<_, Command>(
r#"
INSERT INTO commands (agent_id, command_type, command_text, status, created_by)
VALUES ($1, $2, $3, 'pending', $4)
RETURNING *
"#,
)
.bind(cmd.agent_id)
.bind(&cmd.command_type)
.bind(&cmd.command_text)
.bind(cmd.created_by)
.fetch_one(pool)
.await
}
/// Get a command by ID
pub async fn get_command_by_id(pool: &PgPool, id: Uuid) -> Result<Option<Command>, sqlx::Error> {
sqlx::query_as::<_, Command>("SELECT * FROM commands WHERE id = $1")
.bind(id)
.fetch_optional(pool)
.await
}
/// Get pending commands for an agent
pub async fn get_pending_commands(
pool: &PgPool,
agent_id: Uuid,
) -> Result<Vec<Command>, sqlx::Error> {
sqlx::query_as::<_, Command>(
r#"
SELECT * FROM commands
WHERE agent_id = $1 AND status = 'pending'
ORDER BY created_at ASC
"#,
)
.bind(agent_id)
.fetch_all(pool)
.await
}
/// Get command history for an agent
pub async fn get_agent_commands(
pool: &PgPool,
agent_id: Uuid,
limit: i64,
) -> Result<Vec<Command>, sqlx::Error> {
sqlx::query_as::<_, Command>(
r#"
SELECT * FROM commands
WHERE agent_id = $1
ORDER BY created_at DESC
LIMIT $2
"#,
)
.bind(agent_id)
.bind(limit)
.fetch_all(pool)
.await
}
/// Get all recent commands
pub async fn get_recent_commands(pool: &PgPool, limit: i64) -> Result<Vec<Command>, sqlx::Error> {
sqlx::query_as::<_, Command>(
r#"
SELECT * FROM commands
ORDER BY created_at DESC
LIMIT $1
"#,
)
.bind(limit)
.fetch_all(pool)
.await
}
/// Update command status to running
pub async fn mark_command_running(pool: &PgPool, id: Uuid) -> Result<(), sqlx::Error> {
sqlx::query("UPDATE commands SET status = 'running', started_at = NOW() WHERE id = $1")
.bind(id)
.execute(pool)
.await?;
Ok(())
}
/// Update command result
#[derive(Debug, Clone, Deserialize)]
pub struct CommandResult {
pub exit_code: i32,
pub stdout: String,
pub stderr: String,
}
pub async fn update_command_result(
pool: &PgPool,
id: Uuid,
result: CommandResult,
) -> Result<(), sqlx::Error> {
let status = if result.exit_code == 0 {
"completed"
} else {
"failed"
};
sqlx::query(
r#"
UPDATE commands
SET status = $1, exit_code = $2, stdout = $3, stderr = $4, completed_at = NOW()
WHERE id = $5
"#,
)
.bind(status)
.bind(result.exit_code)
.bind(&result.stdout)
.bind(&result.stderr)
.bind(id)
.execute(pool)
.await?;
Ok(())
}
/// Delete a command
pub async fn delete_command(pool: &PgPool, id: Uuid) -> Result<bool, sqlx::Error> {
let result = sqlx::query("DELETE FROM commands WHERE id = $1")
.bind(id)
.execute(pool)
.await?;
Ok(result.rows_affected() > 0)
}