     1→# GuruConnect PostgreSQL Database Integration Plan
     2→
     3→## Overview
     4→
     5→Add PostgreSQL persistence to GuruConnect for:
     6→1. **Machine persistence** - Agents survive server reboots (show as offline until reconnect)
     7→2. **Audit logging** - Track all technician connections, disconnections, session durations
     8→3. **Support code history** - Persist codes across restarts
     9→
    10→## Database Choice
    11→
    12→**PostgreSQL** - Use existing instance on build server (172.16.3.30) alongside GuruRMM.
    13→
    14→## Schema Design
    15→
    16→### Tables (prefixed with `connect_` to avoid conflicts)
    17→
    18→```sql
    19→-- Machines - persistent agent records
    20→connect_machines (
    21→    id UUID PRIMARY KEY,
    22→    agent_id VARCHAR(255) UNIQUE NOT NULL,  -- Hardware-derived ID
    23→    hostname VARCHAR(255) NOT NULL,
    24→    os_version VARCHAR(255),
    25→    is_elevated BOOLEAN,
    26→    is_persistent BOOLEAN DEFAULT TRUE,
    27→    first_seen TIMESTAMPTZ,
    28→    last_seen TIMESTAMPTZ,
    29→    status VARCHAR(20) DEFAULT 'offline'    -- 'online', 'offline'
    30→)
    31→
    32→-- Sessions - connection history
    33→connect_sessions (
    34→    id UUID PRIMARY KEY,                    -- Matches runtime SessionId
    35→    machine_id UUID REFERENCES connect_machines,
    36→    started_at TIMESTAMPTZ,
    37→    ended_at TIMESTAMPTZ,
    38→    duration_secs INTEGER,
    39→    is_support_session BOOLEAN,
    40→    support_code VARCHAR(10),
    41→    status VARCHAR(20)                      -- 'active', 'ended', 'disconnected'
    42→)
    43→
    44→-- Audit events - comprehensive logging
    45→connect_session_events (
    46→    id BIGSERIAL PRIMARY KEY,
    47→    session_id UUID REFERENCES connect_sessions,
    48→    event_type VARCHAR(50),                 -- session_started, viewer_joined, etc.
    49→    timestamp TIMESTAMPTZ,
    50→    viewer_id VARCHAR(255),
    51→    viewer_name VARCHAR(255),
    52→    details JSONB
    53→)
    54→
    55→-- Support codes - persistent
    56→connect_support_codes (
    57→    id UUID PRIMARY KEY,
    58→    code VARCHAR(10) UNIQUE NOT NULL,
    59→    session_id UUID,
    60→    created_by VARCHAR(255),
    61→    status VARCHAR(20),                     -- pending, connected, completed, cancelled
    62→    client_name VARCHAR(255),
    63→    connected_at TIMESTAMPTZ
    64→)
    65→```
    66→
    67→## Implementation Steps
    68→
    69→### Step 1: Create Database on Build Server
    70→```bash
    71→sudo -u postgres psql
    72→CREATE DATABASE guruconnect;
    73→CREATE USER guruconnect WITH PASSWORD 'secure_password';
    74→GRANT ALL PRIVILEGES ON DATABASE guruconnect TO guruconnect;
    75→\c guruconnect
    76→GRANT ALL ON SCHEMA public TO guruconnect;
    77→```
    78→
    79→### Step 2: Create Migration File
    80→**File:** `server/migrations/001_initial_schema.sql`
    81→- Create all four tables with indexes
    82→- Add updated_at trigger for machines
    83→
    84→### Step 3: Expand Database Module
    85→**Files to create:**
    86→- `src/db/mod.rs` - Database struct with PgPool, connect/migrate methods
    87→- `src/db/machines.rs` - upsert_machine, get_all_machines, mark_offline
    88→- `src/db/sessions.rs` - create_session, end_session
    89→- `src/db/events.rs` - log_event (audit trail)
    90→- `src/db/support_codes.rs` - create/update/list codes
    91→
    92→### Step 4: Update Main.rs
    93→- Initialize database connection pool (if DATABASE_URL set)
    94→- Run migrations on startup
    95→- Restore persistent machines from DB as offline sessions
    96→- Restore active support codes from DB
    97→- Pass `db: Option<Database>` to handlers
    98→
    99→### Step 5: Update SessionManager
   100→**File:** `src/session/mod.rs`
   101→- Add `restore_offline_machine()` method for startup recovery
   102→
   103→### Step 6: Update Relay Handlers
   104→**File:** `src/relay/mod.rs`
   105→- Agent connect: upsert_machine, create_session, log session_started
   106→- Agent disconnect: end_session, mark_offline, log session_ended
   107→- Viewer join: log viewer_joined event
   108→- Viewer leave: log viewer_left event
   109→
   110→### Step 7: Update Support Codes
   111→**File:** `src/support_codes.rs`
   112→- Add `create_code_with_db()` for persistence
   113→- Add `restore_from_db()` for startup recovery
   114→- Sync status changes to database
   115→
   116→### Step 8: Update Config
   117→**File:** `src/config.rs`
   118→- Add DATABASE_MAX_CONNECTIONS (default: 5)
   119→
   120→## Environment Variables
   121→
   122→```bash
   123→DATABASE_URL=postgres://guruconnect:password@localhost:5432/guruconnect
   124→DATABASE_MAX_CONNECTIONS=5
   125→```
   126→
   127→## Key Design Decisions
   128→
   129→1. **Optional database** - Server works without DB if DATABASE_URL not set
   130→2. **Hybrid storage** - In-memory for real-time, DB for persistence
   131→3. **Prefix tables** - `connect_*` to coexist with GuruRMM
   132→4. **Fire-and-forget logging** - DB writes don't block WebSocket handling
   133→5. **Restore on startup** - Persistent machines appear offline until reconnect
   134→
   135→## Critical Files to Modify
   136→
   137→| File | Changes |
   138→|------|---------|
   139→| `server/migrations/001_initial_schema.sql` | CREATE (new) |
   140→| `server/src/db/mod.rs` | Expand with pool + submodules |
   141→| `server/src/db/machines.rs` | CREATE (new) |
   142→| `server/src/db/sessions.rs` | CREATE (new) |
   143→| `server/src/db/events.rs` | CREATE (new) |
   144→| `server/src/db/support_codes.rs` | CREATE (new) |
   145→| `server/src/main.rs` | Add DB init, restore logic |
   146→| `server/src/session/mod.rs` | Add restore_offline_machine |
   147→| `server/src/relay/mod.rs` | Add event logging |
   148→| `server/src/support_codes.rs` | Add DB methods |
   149→| `server/src/config.rs` | Add pool size config |
   150→
   151→## Testing
   152→
   153→1. Start server with DATABASE_URL set
   154→2. Connect agent - verify machine appears in connect_machines
   155→3. Restart server - verify machine shows as offline
   156→4. Reconnect agent - verify machine shows online
   157→5. Connect viewer - verify viewer_joined event logged
   158→6. Disconnect viewer - verify viewer_left event logged
   159→7. Check connect_session_events for audit trail
   160→

<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>

# GuruConnect PostgreSQL Database Integration Plan

## Overview

Add PostgreSQL persistence to GuruConnect for:
1. **Machine persistence** - Agents survive server reboots (show as offline until reconnect)
2. **Audit logging** - Track all technician connections, disconnections, session durations
3. **Support code history** - Persist codes across restarts

## Database Choice

**PostgreSQL** - Use existing instance on build server (172.16.3.30) alongside GuruRMM.

## Schema Design

### Tables (prefixed with `connect_` to avoid conflicts)

```sql
-- Machines - persistent agent records
connect_machines (
    id UUID PRIMARY KEY,
    agent_id VARCHAR(255) UNIQUE NOT NULL,  -- Hardware-derived ID
    hostname VARCHAR(255) NOT NULL,
    os_version VARCHAR(255),
    is_elevated BOOLEAN,
    is_persistent BOOLEAN DEFAULT TRUE,
    first_seen TIMESTAMPTZ,
    last_seen TIMESTAMPTZ,
    status VARCHAR(20) DEFAULT 'offline'    -- 'online', 'offline'
)

-- Sessions - connection history
connect_sessions (
    id UUID PRIMARY KEY,                    -- Matches runtime SessionId
    machine_id UUID REFERENCES connect_machines,
    started_at TIMESTAMPTZ,
    ended_at TIMESTAMPTZ,
    duration_secs INTEGER,
    is_support_session BOOLEAN,
    support_code VARCHAR(10),
    status VARCHAR(20)                      -- 'active', 'ended', 'disconnected'
)

-- Audit events - comprehensive logging
connect_session_events (
    id BIGSERIAL PRIMARY KEY,
    session_id UUID REFERENCES connect_sessions,
    event_type VARCHAR(50),                 -- session_started, viewer_joined, etc.
    timestamp TIMESTAMPTZ,
    viewer_id VARCHAR(255),
    viewer_name VARCHAR(255),
    details JSONB
)

-- Support codes - persistent
connect_support_codes (
    id UUID PRIMARY KEY,
    code VARCHAR(10) UNIQUE NOT NULL,
    session_id UUID,
    created_by VARCHAR(255),
    status VARCHAR(20),                     -- pending, connected, completed, cancelled
    client_name VARCHAR(255),
    connected_at TIMESTAMPTZ
)
```

## Implementation Steps

### Step 1: Create Database on Build Server
```bash
sudo -u postgres psql
CREATE DATABASE guruconnect;
CREATE USER guruconnect WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE guruconnect TO guruconnect;
\c guruconnect
GRANT ALL ON SCHEMA public TO guruconnect;
```

### Step 2: Create Migration File
**File:** `server/migrations/001_initial_schema.sql`
- Create all four tables with indexes
- Add updated_at trigger for machines

### Step 3: Expand Database Module
**Files to create:**
- `src/db/mod.rs` - Database struct with PgPool, connect/migrate methods
- `src/db/machines.rs` - upsert_machine, get_all_machines, mark_offline
- `src/db/sessions.rs` - create_session, end_session
- `src/db/events.rs` - log_event (audit trail)
- `src/db/support_codes.rs` - create/update/list codes

### Step 4: Update Main.rs
- Initialize database connection pool (if DATABASE_URL set)
- Run migrations on startup
- Restore persistent machines from DB as offline sessions
- Restore active support codes from DB
- Pass `db: Option<Database>` to handlers

### Step 5: Update SessionManager
**File:** `src/session/mod.rs`
- Add `restore_offline_machine()` method for startup recovery

### Step 6: Update Relay Handlers
**File:** `src/relay/mod.rs`
- Agent connect: upsert_machine, create_session, log session_started
- Agent disconnect: end_session, mark_offline, log session_ended
- Viewer join: log viewer_joined event
- Viewer leave: log viewer_left event

### Step 7: Update Support Codes
**File:** `src/support_codes.rs`
- Add `create_code_with_db()` for persistence
- Add `restore_from_db()` for startup recovery
- Sync status changes to database

### Step 8: Update Config
**File:** `src/config.rs`
- Add DATABASE_MAX_CONNECTIONS (default: 5)

## Environment Variables

```bash
DATABASE_URL=postgres://guruconnect:password@localhost:5432/guruconnect
DATABASE_MAX_CONNECTIONS=5
```

## Key Design Decisions

1. **Optional database** - Server works without DB if DATABASE_URL not set
2. **Hybrid storage** - In-memory for real-time, DB for persistence
3. **Prefix tables** - `connect_*` to coexist with GuruRMM
4. **Fire-and-forget logging** - DB writes don't block WebSocket handling
5. **Restore on startup** - Persistent machines appear offline until reconnect

## Critical Files to Modify

| File | Changes |
|------|---------|
| `server/migrations/001_initial_schema.sql` | CREATE (new) |
| `server/src/db/mod.rs` | Expand with pool + submodules |
| `server/src/db/machines.rs` | CREATE (new) |
| `server/src/db/sessions.rs` | CREATE (new) |
| `server/src/db/events.rs` | CREATE (new) |
| `server/src/db/support_codes.rs` | CREATE (new) |
| `server/src/main.rs` | Add DB init, restore logic |
| `server/src/session/mod.rs` | Add restore_offline_machine |
| `server/src/relay/mod.rs` | Add event logging |
| `server/src/support_codes.rs` | Add DB methods |
| `server/src/config.rs` | Add pool size config |

## Testing

1. Start server with DATABASE_URL set
2. Connect agent - verify machine appears in connect_machines
3. Restart server - verify machine shows as offline
4. Reconnect agent - verify machine shows online
5. Connect viewer - verify viewer_joined event logged
6. Disconnect viewer - verify viewer_left event logged
7. Check connect_session_events for audit trail


This is the old database integration plan which is now complete. I need to start fresh with a new plan for creating a Windows Service for Ctrl+Alt+Del support.

Let me explore the codebase to understand:
1. How the current agent is structured
2. How Windows services work in Rust
3. How the SAS (Secure Attention Sequence) API works
4. How IPC between the service and agent should work


This is the old completed database plan. I need to start fresh for the Windows Service implementation. Let me explore the agent codebase:


Task

Explore agent structure for service

Prompt:
Explore the GuruConnect agent codebase to understand:

1. The current agent structure in `agent/src/` - main.rs, how it starts, modules
2. The existing keyboard.rs implementation for send_sas()
3. How the agent handles startup persistence (startup.rs)
4. The Cargo.toml dependencies

I need to understand the patterns used so I can design a Windows Service that:
- Runs as SYSTEM to call SendSAS for Ctrl+Alt+Del
- Communicates with the agent via named pipe IPC
- Can be installed/uninstalled by the agent

Focus on understanding the existing code patterns, Windows API usage, and service-related code if any exists.


Perfect! Now I have all the information needed. Let me create a comprehensive exploration report.

## GuruConnect Agent Codebase Exploration Report

Based on my thorough exploration of the GuruConnect agent codebase, here's what I found:

### 1. **Current Agent Structure** (`C:\Users\MikeSwanson\Claude\guru-connect\agent\src\`)

**Key Modules:**
- **main.rs** - Entry point with elevation checks, support code extraction, configuration loading
- **input/keyboard.rs** - Keyboard input via SendInput API
- **input/mod.rs** - Combined input controller (mouse + keyboard)
- **startup.rs** - Registry-based persistence via `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`
- **session/mod.rs** - Core session manager with idle/streaming states
- **transport/websocket.rs** - WebSocket client with protobuf serialization
- **config.rs** - TOML-based configuration (server_url, api_key, agent_id, capture/encoding settings)
- **tray/mod.rs** - System tray with menu (status, end session, debug window)
- **capture/** - DXGI + GDI screen capture implementation
- **encoder/** - Raw + Zstd compression with dirty rectangle detection

### 2. **SendSAS() Implementation** (keyboard.rs, lines 130-178)

**Current Approach:**
```rust
// Tries sas.dll first (dynamic loading):
// 1. LoadLibraryW("sas.dll")
// 2. GetProcAddress("SendSAS")
// 3. Call SendSAS(0) - FALSE for Ctrl+Alt+Del
// Falls back to manual key sending (won't work without privileges)
```

**Critical Issues:**
- Requires administrator privileges to work via sas.dll
- Fallback to SendInput won't bypass Windows security restrictions
- Designed to run as the logged-in user, NOT as SYSTEM

### 3. **Startup Persistence** (startup.rs)

**Pattern Used:**
- Adds executable to `HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Run`
- Runs in user context, not SYSTEM
- Handles removal and scheduled deletion via `MoveFileExW` with `MOVEFILE_DELAY_UNTIL_REBOOT`

### 4. **Cargo.toml Dependencies**

**Relevant for your service design:**
```toml
# Already included:
windows = "0.58" with features for:
  - Win32_UI_Input_KeyboardAndMouse (SendInput)
  - Win32_System_Registry (startup)
  - Win32_System_Threading (token elevation checks)
  - Win32_System_Console
  - Win32_Storage_FileSystem
windows-service = "0.7"  # Already available!
```

### 5. **IPC/Communication Patterns**

**Current:**
- WebSocket over TLS to server (remote)
- No local IPC currently implemented
- Uses protobuf for message serialization

**Proto Message Types (guruconnect.proto):**
- SessionRequest/Response
- MouseEvent / KeyEvent
- VideoFrame (with display_id)
- ChatMessage
- Heartbeat / HeartbeatAck

### 6. **Reference Implementation: GuruRMM Agent Service**

The GuruRMM agent (`C:\Users\MikeSwanson\Claude\gururmm\agent\src\service.rs`) provides excellent patterns:

**Windows Service Implementation:**
```rust
// Uses windows-service crate:
define_windows_service!(ffi_service_main, service_main);
service_dispatcher::start(SERVICE_NAME, ffi_service_main)

// Service lifecycle:
// 1. ServiceState::StartPending (with 10s wait_hint)
// 2. ServiceState::Running (accepts STOP + SHUTDOWN)
// 3. Handles ServiceControl::Stop, Interrogate, Shutdown events

// Key files:
// SERVICE_NAME = "GuruRMMAgent"
// INSTALL_DIR = r"C:\Program Files\GuruRMM"
// CONFIG_DIR = r"C:\ProgramData\GuruRMM"
```

---

## Design Recommendations for Windows Service

Based on these patterns, here's what I recommend:

### **Service Architecture:**

1. **Create `sas-service` as separate binary** (similar to how GuruConnect is structured)
   - Runs as SYSTEM
   - Runs as Windows Service (using `windows-service` crate already in Cargo.toml)
   - Minimal footprint - only handles SAS calls

2. **IPC via Named Pipes** (Windows standard for this)
   ```
   Agent (user context)
   └─> \\.\pipe\guruconnect-sas (named pipe)
       └─> SAS Service (SYSTEM context)
           └─> SendSAS() via sas.dll
   ```

3. **Service Control Flow:**
   - Agent calls `CreateFile()` on named pipe
   - Sends request: `{"action": "send_sas"}`
   - Service calls `SendSAS(0)`
   - Returns success/error

4. **Installation/Uninstallment:**
   - Agent calls: `sc.exe create GuruConnectSAS ...`
   - Agent calls: `sc.exe start GuruConnectSAS`
   - On uninstall: `sc.exe delete GuruConnectSAS`

### **Key Integration Points:**

1. **Modify keyboard.rs**: Add method to call service instead of direct sas.dll
2. **New module**: `sas_service/` - Named pipe server running as Windows Service
3. **Update startup.rs**: Install/uninstall the service alongside agent
4. **Update Cargo.toml**: Already has `windows-service` and `windows` crates needed

### **Files to Reference:**
- GuruRMM service pattern: `C:\Users\MikeSwanson\Claude\gururmm\agent\src\service.rs`
- Current keyboard: `C:\Users\MikeSwanson\Claude\guru-connect\agent\src\input\keyboard.rs`
- Current startup: `C:\Users\MikeSwanson\Claude\guru-connect\agent\src\startup.rs`

This approach maintains the clean separation of concerns while leveraging existing crate dependencies and proven Windows service patterns from the GuruRMM agent.

agentId: a772d6e (for resuming to continue this agent's work if needed)

Prompt:
Explore the GuruConnect agent codebase to understand:

1. The current agent structure in `agent/src/` - main.rs, how it starts, modules
2. The existing keyboard.rs implementation for send_sas()
3. How the agent handles startup persistence (startup.rs)
4. The Cargo.toml dependencies

I need to understand the patterns used so I can design a Windows Service that:
- Runs as SYSTEM to call SendSAS for Ctrl+Alt+Del
- Communicates with the agent via named pipe IPC
- Can be installed/uninstalled by the agent

Focus on understanding the existing code patterns, Windows API usage, and service-related code if any exists.


The exploration agent provided excellent information. Now I have a clear understanding of:

1. The existing agent structure
2. The windows-service crate is already available
3. GuruRMM has a working service implementation I can reference
4. Named pipes are the right IPC mechanism

Let me now write the plan to the plan file. I'll design a Windows Service that:
1. Runs as SYSTEM
2. Listens on a named pipe
3. Handles SendSAS requests from the agent
4. Can be installed/uninstalled by the agent


Excellent exploration! Now let me write the plan:


Write

# GuruConnect SAS Service Plan

## Overview

Create a Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence) requests. The agent communicates with the service via named pipe IPC.

## Why a Service is Required

- `SendSAS()` API requires SYSTEM privileges or `SeTcbPrivilege`
- Running as Administrator is NOT sufficient
- Windows blocks Ctrl+Alt+Del key injection for security
- All professional remote desktop tools (ScreenConnect, TeamViewer) use this pattern

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect Agent (runs as logged-in user)                 │
│  - Receives SpecialKey message from server                  │
│  - Connects to named pipe: \\.\pipe\guruconnect-sas         │
│  - Sends "sas" command                                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect SAS Service (runs as SYSTEM)                   │
│  - Windows Service: "GuruConnectSAS"                        │
│  - Listens on named pipe                                    │
│  - Calls SendSAS(0) via sas.dll                             │
│  - Returns success/error                                    │
└─────────────────────────────────────────────────────────────┘
```

## Implementation

### New Binary: `guruconnect-sas-service`

**Location:** `agent/src/bin/sas_service.rs` (or separate crate)

**Functionality:**
1. Windows Service using `windows-service` crate (already in Cargo.toml)
2. Named pipe server at `\\.\pipe\guruconnect-sas`
3. Accepts commands: `sas` (send Ctrl+Alt+Del)
4. Loads `sas.dll` and calls `SendSAS(0)`

### Service Details

```rust
// Service constants
const SERVICE_NAME: &str = "GuruConnectSAS";
const SERVICE_DISPLAY: &str = "GuruConnect SAS Service";
const SERVICE_DESC: &str = "Handles Secure Attention Sequence for GuruConnect remote sessions";
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";

// Install location
const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";
```

### Files to Create/Modify

| File | Action | Purpose |
|------|--------|---------|
| `agent/src/bin/sas_service.rs` | CREATE | Windows Service binary |
| `agent/src/sas_client.rs` | CREATE | Named pipe client for agent |
| `agent/src/input/keyboard.rs` | MODIFY | Use sas_client instead of direct call |
| `agent/src/startup.rs` | MODIFY | Install/uninstall service |
| `agent/Cargo.toml` | MODIFY | Add binary target |

### Step-by-Step Implementation

#### Step 1: Create SAS Service Binary (`sas_service.rs`)

```rust
// Key components:
// 1. Windows service boilerplate (define_windows_service!, service_dispatcher)
// 2. Named pipe server loop
// 3. SendSAS implementation
// 4. Install/uninstall subcommands

fn main() {
    match std::env::args().nth(1).as_deref() {
        Some("install") => install_service(),
        Some("uninstall") => uninstall_service(),
        Some("run") => run_service(),  // Called by SCM
        _ => print_usage(),
    }
}
```

#### Step 2: Named Pipe Server

```rust
// Create pipe with SYSTEM security (allow all users to connect)
let pipe = CreateNamedPipeW(
    PIPE_NAME,
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    1,  // Max instances
    512, 512,  // Buffer sizes
    0,  // Default timeout
    Some(&security_attributes),  // Allow non-admin connections
);

loop {
    ConnectNamedPipe(pipe, None);
    // Read command, execute, respond
    DisconnectNamedPipe(pipe);
}
```

#### Step 3: SendSAS Implementation

```rust
fn send_sas() -> Result<()> {
    unsafe {
        let lib = LoadLibraryW(w!("sas.dll"))?;
        let proc = GetProcAddress(lib, s!("SendSAS"))
            .ok_or_else(|| anyhow!("SendSAS not found"))?;
        let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
        send_sas(0);  // 0 = Ctrl+Alt+Del
        Ok(())
    }
}
```

#### Step 4: Agent SAS Client (`sas_client.rs`)

```rust
pub fn request_sas() -> Result<()> {
    let mut pipe = File::options()
        .read(true)
        .write(true)
        .open(r"\\.\pipe\guruconnect-sas")?;

    pipe.write_all(b"sas\n")?;

    let mut response = [0u8; 64];
    let n = pipe.read(&mut response)?;

    if &response[..n] == b"ok\n" {
        Ok(())
    } else {
        Err(anyhow!("SAS service error"))
    }
}
```

#### Step 5: Update keyboard.rs

```rust
pub fn send_sas(&mut self) -> Result<()> {
    // Try service first
    if let Ok(()) = crate::sas_client::request_sas() {
        return Ok(());
    }

    // Fallback to direct call (for elevated sessions)
    // ... existing code ...
}
```

#### Step 6: Service Installation (startup.rs)

```rust
pub fn install_sas_service() -> Result<()> {
    // Copy sas_service.exe to C:\Program Files\GuruConnect\
    // Run: sc create GuruConnectSAS binPath= "..." start= auto
    // Run: sc start GuruConnectSAS
}

pub fn uninstall_sas_service() -> Result<()> {
    // Run: sc stop GuruConnectSAS
    // Run: sc delete GuruConnectSAS
    // Remove exe file
}
```

### Cargo.toml Changes

```toml
[[bin]]
name = "guruconnect-sas-service"
path = "src/bin/sas_service.rs"

[dependencies]
# Already present:
windows-service = "0.7"
windows = { version = "0.58", features = [...] }
```

### Security Considerations

1. **Pipe Security:** Create pipe with DACL allowing authenticated users
2. **Service Account:** Runs as LocalSystem (has SeTcbPrivilege)
3. **Command Validation:** Only accept "sas" command, nothing else
4. **No Sensitive Data:** Pipe only carries simple commands

### Installation Flow

1. Agent starts (persistent mode)
2. Checks if SAS service installed
3. If not, copies service exe and installs via `sc.exe`
4. Starts service
5. Future Ctrl+Alt+Del requests go through pipe

### Testing

1. Build both binaries
2. Install service: `guruconnect-sas-service.exe install`
3. Connect agent, join viewer
4. Click Ctrl+Alt+Del button
5. Verify Windows security screen appears

### Reference Files

- GuruRMM service pattern: `gururmm/agent/src/service.rs`
- Current keyboard: `guru-connect/agent/src/input/keyboard.rs`
- Named pipe examples: Windows API documentation


The file C:\Users\MikeSwanson\.claude\plans\velvet-tumbling-tide.md has been updated. Here's the result of running `cat -n` on a snippet of the edited file:
     1→# GuruConnect SAS Service Plan
     2→
     3→## Overview
     4→
     5→Create a Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence) requests. The agent communicates with the service via named pipe IPC.
     6→
     7→## Why a Service is Required
     8→
     9→- `SendSAS()` API requires SYSTEM privileges or `SeTcbPrivilege`
    10→- Running as Administrator is NOT sufficient
    11→- Windows blocks Ctrl+Alt+Del key injection for security
    12→- All professional remote desktop tools (ScreenConnect, TeamViewer) use this pattern
    13→
    14→## Architecture
    15→
    16→```
    17→┌─────────────────────────────────────────────────────────────┐
    18→│  GuruConnect Agent (runs as logged-in user)                 │
    19→│  - Receives SpecialKey message from server                  │
    20→│  - Connects to named pipe: \\.\pipe\guruconnect-sas         │
    21→│  - Sends "sas" command                                      │
    22→└─────────────────────────────────────────────────────────────┘
    23→                              │
    24→                              ▼
    25→┌─────────────────────────────────────────────────────────────┐
    26→│  GuruConnect SAS Service (runs as SYSTEM)                   │
    27→│  - Windows Service: "GuruConnectSAS"                        │
    28→│  - Listens on named pipe                                    │
    29→│  - Calls SendSAS(0) via sas.dll                             │
    30→│  - Returns success/error                                    │
    31→└─────────────────────────────────────────────────────────────┘
    32→```
    33→
    34→## Implementation
    35→
    36→### New Binary: `guruconnect-sas-service`
    37→
    38→**Location:** `agent/src/bin/sas_service.rs` (or separate crate)
    39→
    40→**Functionality:**
    41→1. Windows Service using `windows-service` crate (already in Cargo.toml)
    42→2. Named pipe server at `\\.\pipe\guruconnect-sas`
    43→3. Accepts commands: `sas` (send Ctrl+Alt+Del)
    44→4. Loads `sas.dll` and calls `SendSAS(0)`
    45→
    46→### Service Details
    47→
    48→```rust
    49→// Service constants
    50→const SERVICE_NAME: &str = "GuruConnectSAS";
    51→const SERVICE_DISPLAY: &str = "GuruConnect SAS Service";
    52→const SERVICE_DESC: &str = "Handles Secure Attention Sequence for GuruConnect remote sessions";
    53→const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";
    54→
    55→// Install location
    56→const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";
    57→```
    58→
    59→### Files to Create/Modify
    60→
    61→| File | Action | Purpose |
    62→|------|--------|---------|
    63→| `agent/src/bin/sas_service.rs` | CREATE | Windows Service binary |
    64→| `agent/src/sas_client.rs` | CREATE | Named pipe client for agent |
    65→| `agent/src/input/keyboard.rs` | MODIFY | Use sas_client instead of direct call |
    66→| `agent/src/startup.rs` | MODIFY | Install/uninstall service |
    67→| `agent/Cargo.toml` | MODIFY | Add binary target |
    68→
    69→### Step-by-Step Implementation
    70→
    71→#### Step 1: Create SAS Service Binary (`sas_service.rs`)
    72→
    73→```rust
    74→// Key components:
    75→// 1. Windows service boilerplate (define_windows_service!, service_dispatcher)
    76→// 2. Named pipe server loop
    77→// 3. SendSAS implementation
    78→// 4. Install/uninstall subcommands
    79→
    80→fn main() {
    81→    match std::env::args().nth(1).as_deref() {
    82→        Some("install") => install_service(),
    83→        Some("uninstall") => uninstall_service(),
    84→        Some("run") => run_service(),  // Called by SCM
    85→        _ => print_usage(),
    86→    }
    87→}
    88→```
    89→
    90→#### Step 2: Named Pipe Server
    91→
    92→```rust
    93→// Create pipe with SYSTEM security (allow all users to connect)
    94→let pipe = CreateNamedPipeW(
    95→    PIPE_NAME,
    96→    PIPE_ACCESS_DUPLEX,
    97→    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    98→    1,  // Max instances
    99→    512, 512,  // Buffer sizes
   100→    0,  // Default timeout
   101→    Some(&security_attributes),  // Allow non-admin connections
   102→);
   103→
   104→loop {
   105→    ConnectNamedPipe(pipe, None);
   106→    // Read command, execute, respond
   107→    DisconnectNamedPipe(pipe);
   108→}
   109→```
   110→
   111→#### Step 3: SendSAS Implementation
   112→
   113→```rust
   114→fn send_sas() -> Result<()> {
   115→    unsafe {
   116→        let lib = LoadLibraryW(w!("sas.dll"))?;
   117→        let proc = GetProcAddress(lib, s!("SendSAS"))
   118→            .ok_or_else(|| anyhow!("SendSAS not found"))?;
   119→        let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
   120→        send_sas(0);  // 0 = Ctrl+Alt+Del
   121→        Ok(())
   122→    }
   123→}
   124→```
   125→
   126→#### Step 4: Agent SAS Client (`sas_client.rs`)
   127→
   128→```rust
   129→pub fn request_sas() -> Result<()> {
   130→    let mut pipe = File::options()
   131→        .read(true)
   132→        .write(true)
   133→        .open(r"\\.\pipe\guruconnect-sas")?;
   134→
   135→    pipe.write_all(b"sas\n")?;
   136→
   137→    let mut response = [0u8; 64];
   138→    let n = pipe.read(&mut response)?;
   139→
   140→    if &response[..n] == b"ok\n" {
   141→        Ok(())
   142→    } else {
   143→        Err(anyhow!("SAS service error"))
   144→    }
   145→}
   146→```
   147→
   148→#### Step 5: Update keyboard.rs
   149→
   150→```rust
   151→pub fn send_sas(&mut self) -> Result<()> {
   152→    // Try service first
   153→    if let Ok(()) = crate::sas_client::request_sas() {
   154→        return Ok(());
   155→    }
   156→
   157→    // Fallback to direct call (for elevated sessions)
   158→    // ... existing code ...
   159→}
   160→```
   161→
   162→#### Step 6: Service Installation (startup.rs)
   163→
   164→```rust
   165→pub fn install_sas_service() -> Result<()> {
   166→    // Copy sas_service.exe to C:\Program Files\GuruConnect\
   167→    // Run: sc create GuruConnectSAS binPath= "..." start= auto
   168→    // Run: sc start GuruConnectSAS
   169→}
   170→
   171→pub fn uninstall_sas_service() -> Result<()> {
   172→    // Run: sc stop GuruConnectSAS
   173→    // Run: sc delete GuruConnectSAS
   174→    // Remove exe file
   175→}
   176→```
   177→
   178→### Cargo.toml Changes
   179→
   180→```toml
   181→[[bin]]
   182→name = "guruconnect-sas-service"
   183→path = "src/bin/sas_service.rs"
   184→
   185→[dependencies]
   186→# Already present:
   187→windows-service = "0.7"
   188→windows = { version = "0.58", features = [...] }
   189→```
   190→
   191→### Security Considerations
   192→
   193→1. **Pipe Security:** Create pipe with DACL allowing authenticated users
   194→2. **Service Account:** Runs as LocalSystem (has SeTcbPrivilege)
   195→3. **Command Validation:** Only accept "sas" command, nothing else
   196→4. **No Sensitive Data:** Pipe only carries simple commands
   197→
   198→### Installation Flow
   199→
   200→1. Agent starts (persistent mode)
   201→2. Checks if SAS service installed
   202→3. If not, copies service exe and installs via `sc.exe`
   203→4. Starts service
   204→5. Future Ctrl+Alt+Del requests go through pipe
   205→
   206→### Testing
   207→
   208→1. Build both binaries
   209→2. Install service: `guruconnect-sas-service.exe install`
   210→3. Connect agent, join viewer
   211→4. Click Ctrl+Alt+Del button
   212→5. Verify Windows security screen appears
   213→
   214→### Reference Files
   215→
   216→- GuruRMM service pattern: `gururmm/agent/src/service.rs`
   217→- Current keyboard: `guru-connect/agent/src/input/keyboard.rs`
   218→- Named pipe examples: Windows API documentation
   219→

# GuruConnect SAS Service Plan

## Overview

Create a Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence) requests. The agent communicates with the service via named pipe IPC.

## Why a Service is Required

- `SendSAS()` API requires SYSTEM privileges or `SeTcbPrivilege`
- Running as Administrator is NOT sufficient
- Windows blocks Ctrl+Alt+Del key injection for security
- All professional remote desktop tools (ScreenConnect, TeamViewer) use this pattern

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect Agent (runs as logged-in user)                 │
│  - Receives SpecialKey message from server                  │
│  - Connects to named pipe: \\.\pipe\guruconnect-sas         │
│  - Sends "sas" command                                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect SAS Service (runs as SYSTEM)                   │
│  - Windows Service: "GuruConnectSAS"                        │
│  - Listens on named pipe                                    │
│  - Calls SendSAS(0) via sas.dll                             │
│  - Returns success/error                                    │
└─────────────────────────────────────────────────────────────┘
```

## Implementation

### New Binary: `guruconnect-sas-service`

**Location:** `agent/src/bin/sas_service.rs` (or separate crate)

**Functionality:**
1. Windows Service using `windows-service` crate (already in Cargo.toml)
2. Named pipe server at `\\.\pipe\guruconnect-sas`
3. Accepts commands: `sas` (send Ctrl+Alt+Del)
4. Loads `sas.dll` and calls `SendSAS(0)`

### Service Details

```rust
// Service constants
const SERVICE_NAME: &str = "GuruConnectSAS";
const SERVICE_DISPLAY: &str = "GuruConnect SAS Service";
const SERVICE_DESC: &str = "Handles Secure Attention Sequence for GuruConnect remote sessions";
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";

// Install location
const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";
```

### Files to Create/Modify

| File | Action | Purpose |
|------|--------|---------|
| `agent/src/bin/sas_service.rs` | CREATE | Windows Service binary |
| `agent/src/sas_client.rs` | CREATE | Named pipe client for agent |
| `agent/src/input/keyboard.rs` | MODIFY | Use sas_client instead of direct call |
| `agent/src/startup.rs` | MODIFY | Install/uninstall service |
| `agent/Cargo.toml` | MODIFY | Add binary target |

### Step-by-Step Implementation

#### Step 1: Create SAS Service Binary (`sas_service.rs`)

```rust
// Key components:
// 1. Windows service boilerplate (define_windows_service!, service_dispatcher)
// 2. Named pipe server loop
// 3. SendSAS implementation
// 4. Install/uninstall subcommands

fn main() {
    match std::env::args().nth(1).as_deref() {
        Some("install") => install_service(),
        Some("uninstall") => uninstall_service(),
        Some("run") => run_service(),  // Called by SCM
        _ => print_usage(),
    }
}
```

#### Step 2: Named Pipe Server

```rust
// Create pipe with SYSTEM security (allow all users to connect)
let pipe = CreateNamedPipeW(
    PIPE_NAME,
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    1,  // Max instances
    512, 512,  // Buffer sizes
    0,  // Default timeout
    Some(&security_attributes),  // Allow non-admin connections
);

loop {
    ConnectNamedPipe(pipe, None);
    // Read command, execute, respond
    DisconnectNamedPipe(pipe);
}
```

#### Step 3: SendSAS Implementation

```rust
fn send_sas() -> Result<()> {
    unsafe {
        let lib = LoadLibraryW(w!("sas.dll"))?;
        let proc = GetProcAddress(lib, s!("SendSAS"))
            .ok_or_else(|| anyhow!("SendSAS not found"))?;
        let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
        send_sas(0);  // 0 = Ctrl+Alt+Del
        Ok(())
    }
}
```

#### Step 4: Agent SAS Client (`sas_client.rs`)

```rust
pub fn request_sas() -> Result<()> {
    let mut pipe = File::options()
        .read(true)
        .write(true)
        .open(r"\\.\pipe\guruconnect-sas")?;

    pipe.write_all(b"sas\n")?;

    let mut response = [0u8; 64];
    let n = pipe.read(&mut response)?;

    if &response[..n] == b"ok\n" {
        Ok(())
    } else {
        Err(anyhow!("SAS service error"))
    }
}
```

#### Step 5: Update keyboard.rs

```rust
pub fn send_sas(&mut self) -> Result<()> {
    // Try service first
    if let Ok(()) = crate::sas_client::request_sas() {
        return Ok(());
    }

    // Fallback to direct call (for elevated sessions)
    // ... existing code ...
}
```

#### Step 6: Service Installation (startup.rs)

```rust
pub fn install_sas_service() -> Result<()> {
    // Copy sas_service.exe to C:\Program Files\GuruConnect\
    // Run: sc create GuruConnectSAS binPath= "..." start= auto
    // Run: sc start GuruConnectSAS
}

pub fn uninstall_sas_service() -> Result<()> {
    // Run: sc stop GuruConnectSAS
    // Run: sc delete GuruConnectSAS
    // Remove exe file
}
```

### Cargo.toml Changes

```toml
[[bin]]
name = "guruconnect-sas-service"
path = "src/bin/sas_service.rs"

[dependencies]
# Already present:
windows-service = "0.7"
windows = { version = "0.58", features = [...] }
```

### Security Considerations

1. **Pipe Security:** Create pipe with DACL allowing authenticated users
2. **Service Account:** Runs as LocalSystem (has SeTcbPrivilege)
3. **Command Validation:** Only accept "sas" command, nothing else
4. **No Sensitive Data:** Pipe only carries simple commands

### Installation Flow

1. Agent starts (persistent mode)
2. Checks if SAS service installed
3. If not, copies service exe and installs via `sc.exe`
4. Starts service
5. Future Ctrl+Alt+Del requests go through pipe

### Testing

1. Build both binaries
2. Install service: `guruconnect-sas-service.exe install`
3. Connect agent, join viewer
4. Click Ctrl+Alt+Del button
5. Verify Windows security screen appears

### Reference Files

- GuruRMM service pattern: `gururmm/agent/src/service.rs`
- Current keyboard: `guru-connect/agent/src/input/keyboard.rs`
- Named pipe examples: Windows API documentation

-# GuruConnect PostgreSQL Database Integration Plan

-Add PostgreSQL persistence to GuruConnect for:

-1. **Machine persistence** - Agents survive server reboots (show as offline until reconnect)

-2. **Audit logging** - Track all technician connections, disconnections, session durations

-3. **Support code history** - Persist codes across restarts

+Create a Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence) requests. The agent communicates with the service via named pipe IPC.

-**PostgreSQL** - Use existing instance on build server (172.16.3.30) alongside GuruRMM.

+- `SendSAS()` API requires SYSTEM privileges or `SeTcbPrivilege`

+- Running as Administrator is NOT sufficient

+- Windows blocks Ctrl+Alt+Del key injection for security

+- All professional remote desktop tools (ScreenConnect, TeamViewer) use this pattern

-### Tables (prefixed with `connect_` to avoid conflicts)

+│  GuruConnect Agent (runs as logged-in user)                 │

+│  - Receives SpecialKey message from server                  │

+│  - Connects to named pipe: \\.\pipe\guruconnect-sas         │

+│  - Sends "sas" command                                      │

+│  GuruConnect SAS Service (runs as SYSTEM)                   │

+│  - Windows Service: "GuruConnectSAS"                        │

+│  - Listens on named pipe                                    │

+│  - Calls SendSAS(0) via sas.dll                             │

+│  - Returns success/error                                    │

-    agent_id VARCHAR(255) UNIQUE NOT NULL,  -- Hardware-derived ID

-    is_persistent BOOLEAN DEFAULT TRUE,

-    status VARCHAR(20) DEFAULT 'offline'    -- 'online', 'offline'

-    id UUID PRIMARY KEY,                    -- Matches runtime SessionId

-    machine_id UUID REFERENCES connect_machines,

-    status VARCHAR(20)                      -- 'active', 'ended', 'disconnected'

+### New Binary: `guruconnect-sas-service`

--- Audit events - comprehensive logging

-    session_id UUID REFERENCES connect_sessions,

-    event_type VARCHAR(50),                 -- session_started, viewer_joined, etc.

+**Location:** `agent/src/bin/sas_service.rs` (or separate crate)

-    status VARCHAR(20),                     -- pending, connected, completed, cancelled

+1. Windows Service using `windows-service` crate (already in Cargo.toml)

+2. Named pipe server at `\\.\pipe\guruconnect-sas`

+3. Accepts commands: `sas` (send Ctrl+Alt+Del)

+4. Loads `sas.dll` and calls `SendSAS(0)`

+const SERVICE_NAME: &str = "GuruConnectSAS";

+const SERVICE_DISPLAY: &str = "GuruConnect SAS Service";

+const SERVICE_DESC: &str = "Handles Secure Attention Sequence for GuruConnect remote sessions";

+const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";

+const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";

-### Step 1: Create Database on Build Server

-CREATE USER guruconnect WITH PASSWORD 'secure_password';

-GRANT ALL PRIVILEGES ON DATABASE guruconnect TO guruconnect;

-GRANT ALL ON SCHEMA public TO guruconnect;

+| `agent/src/bin/sas_service.rs` | CREATE | Windows Service binary |

+| `agent/src/sas_client.rs` | CREATE | Named pipe client for agent |

+| `agent/src/input/keyboard.rs` | MODIFY | Use sas_client instead of direct call |

+| `agent/src/startup.rs` | MODIFY | Install/uninstall service |

+| `agent/Cargo.toml` | MODIFY | Add binary target |

+#### Step 1: Create SAS Service Binary (`sas_service.rs`)

+// 1. Windows service boilerplate (define_windows_service!, service_dispatcher)

+    match std::env::args().nth(1).as_deref() {

+        Some("install") => install_service(),

+        Some("uninstall") => uninstall_service(),

+        Some("run") => run_service(),  // Called by SCM

-- `src/db/mod.rs` - Database struct with PgPool, connect/migrate methods

-- `src/db/machines.rs` - upsert_machine, get_all_machines, mark_offline

-- `src/db/sessions.rs` - create_session, end_session

-- `src/db/events.rs` - log_event (audit trail)

-- `src/db/support_codes.rs` - create/update/list codes

+// Create pipe with SYSTEM security (allow all users to connect)

+    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,

+    Some(&security_attributes),  // Allow non-admin connections

-- Initialize database connection pool (if DATABASE_URL set)

-- Restore persistent machines from DB as offline sessions

-- Pass `db: Option<Database>` to handlers

-- Add `restore_offline_machine()` method for startup recovery

-- Agent connect: upsert_machine, create_session, log session_started

-- Agent disconnect: end_session, mark_offline, log session_ended

+        let lib = LoadLibraryW(w!("sas.dll"))?;

+        let proc = GetProcAddress(lib, s!("SendSAS"))

+            .ok_or_else(|| anyhow!("SendSAS not found"))?;

+        let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);

+        send_sas(0);  // 0 = Ctrl+Alt+Del

-- Add `create_code_with_db()` for persistence

-- Add `restore_from_db()` for startup recovery

+#### Step 4: Agent SAS Client (`sas_client.rs`)

-- Add DATABASE_MAX_CONNECTIONS (default: 5)

+        .open(r"\\.\pipe\guruconnect-sas")?;

+        Err(anyhow!("SAS service error"))

-1. **Optional database** - Server works without DB if DATABASE_URL not set

-2. **Hybrid storage** - In-memory for real-time, DB for persistence

-3. **Prefix tables** - `connect_*` to coexist with GuruRMM

-4. **Fire-and-forget logging** - DB writes don't block WebSocket handling

-5. **Restore on startup** - Persistent machines appear offline until reconnect

+pub fn send_sas(&mut self) -> Result<()> {

+    if let Ok(()) = crate::sas_client::request_sas() {

+    // Fallback to direct call (for elevated sessions)

-| `server/migrations/001_initial_schema.sql` | CREATE (new) |

-| `server/src/db/mod.rs` | Expand with pool + submodules |

-| `server/src/db/machines.rs` | CREATE (new) |

-| `server/src/db/sessions.rs` | CREATE (new) |

-| `server/src/db/events.rs` | CREATE (new) |

-| `server/src/db/support_codes.rs` | CREATE (new) |

-| `server/src/main.rs` | Add DB init, restore logic |

-| `server/src/session/mod.rs` | Add restore_offline_machine |

-| `server/src/relay/mod.rs` | Add event logging |

-| `server/src/support_codes.rs` | Add DB methods |

-| `server/src/config.rs` | Add pool size config |

+#### Step 6: Service Installation (startup.rs)

+pub fn install_sas_service() -> Result<()> {

+    // Copy sas_service.exe to C:\Program Files\GuruConnect\

+    // Run: sc create GuruConnectSAS binPath= "..." start= auto

-2. Connect agent - verify machine appears in connect_machines

-3. Restart server - verify machine shows as offline

-4. Reconnect agent - verify machine shows online

-5. Connect viewer - verify viewer_joined event logged

-6. Disconnect viewer - verify viewer_left event logged

-7. Check connect_session_events for audit trail

+pub fn uninstall_sas_service() -> Result<()> {

+windows = { version = "0.58", features = [...] }

+1. **Pipe Security:** Create pipe with DACL allowing authenticated users

+2. **Service Account:** Runs as LocalSystem (has SeTcbPrivilege)

+3. **Command Validation:** Only accept "sas" command, nothing else

+4. **No Sensitive Data:** Pipe only carries simple commands

+3. If not, copies service exe and installs via `sc.exe`

+5. Future Ctrl+Alt+Del requests go through pipe

+2. Install service: `guruconnect-sas-service.exe install`

+5. Verify Windows security screen appears

+- GuruRMM service pattern: `gururmm/agent/src/service.rs`

+- Current keyboard: `guru-connect/agent/src/input/keyboard.rs`

+- Named pipe examples: Windows API documentation

# GuruConnect PostgreSQL Database Integration Plan

## Overview

Add PostgreSQL persistence to GuruConnect for:
1. **Machine persistence** - Agents survive server reboots (show as offline until reconnect)
2. **Audit logging** - Track all technician connections, disconnections, session durations
3. **Support code history** - Persist codes across restarts

## Database Choice

**PostgreSQL** - Use existing instance on build server (172.16.3.30) alongside GuruRMM.

## Schema Design

### Tables (prefixed with `connect_` to avoid conflicts)

```sql
-- Machines - persistent agent records
connect_machines (
    id UUID PRIMARY KEY,
    agent_id VARCHAR(255) UNIQUE NOT NULL,  -- Hardware-derived ID
    hostname VARCHAR(255) NOT NULL,
    os_version VARCHAR(255),
    is_elevated BOOLEAN,
    is_persistent BOOLEAN DEFAULT TRUE,
    first_seen TIMESTAMPTZ,
    last_seen TIMESTAMPTZ,
    status VARCHAR(20) DEFAULT 'offline'    -- 'online', 'offline'
)

-- Sessions - connection history
connect_sessions (
    id UUID PRIMARY KEY,                    -- Matches runtime SessionId
    machine_id UUID REFERENCES connect_machines,
    started_at TIMESTAMPTZ,
    ended_at TIMESTAMPTZ,
    duration_secs INTEGER,
    is_support_session BOOLEAN,
    support_code VARCHAR(10),
    status VARCHAR(20)                      -- 'active', 'ended', 'disconnected'
)

-- Audit events - comprehensive logging
connect_session_events (
    id BIGSERIAL PRIMARY KEY,
    session_id UUID REFERENCES connect_sessions,
    event_type VARCHAR(50),                 -- session_started, viewer_joined, etc.
    timestamp TIMESTAMPTZ,
    viewer_id VARCHAR(255),
    viewer_name VARCHAR(255),
    details JSONB
)

-- Support codes - persistent
connect_support_codes (
    id UUID PRIMARY KEY,
    code VARCHAR(10) UNIQUE NOT NULL,
    session_id UUID,
    created_by VARCHAR(255),
    status VARCHAR(20),                     -- pending, connected, completed, cancelled
    client_name VARCHAR(255),
    connected_at TIMESTAMPTZ
)
```

## Implementation Steps

### Step 1: Create Database on Build Server
```bash
sudo -u postgres psql
CREATE DATABASE guruconnect;
CREATE USER guruconnect WITH PASSWORD 'secure_password';
GRANT ALL PRIVILEGES ON DATABASE guruconnect TO guruconnect;
\c guruconnect
GRANT ALL ON SCHEMA public TO guruconnect;
```

### Step 2: Create Migration File
**File:** `server/migrations/001_initial_schema.sql`
- Create all four tables with indexes
- Add updated_at trigger for machines

### Step 3: Expand Database Module
**Files to create:**
- `src/db/mod.rs` - Database struct with PgPool, connect/migrate methods
- `src/db/machines.rs` - upsert_machine, get_all_machines, mark_offline
- `src/db/sessions.rs` - create_session, end_session
- `src/db/events.rs` - log_event (audit trail)
- `src/db/support_codes.rs` - create/update/list codes

### Step 4: Update Main.rs
- Initialize database connection pool (if DATABASE_URL set)
- Run migrations on startup
- Restore persistent machines from DB as offline sessions
- Restore active support codes from DB
- Pass `db: Option<Database>` to handlers

### Step 5: Update SessionManager
**File:** `src/session/mod.rs`
- Add `restore_offline_machine()` method for startup recovery

### Step 6: Update Relay Handlers
**File:** `src/relay/mod.rs`
- Agent connect: upsert_machine, create_session, log session_started
- Agent disconnect: end_session, mark_offline, log session_ended
- Viewer join: log viewer_joined event
- Viewer leave: log viewer_left event

### Step 7: Update Support Codes
**File:** `src/support_codes.rs`
- Add `create_code_with_db()` for persistence
- Add `restore_from_db()` for startup recovery
- Sync status changes to database

### Step 8: Update Config
**File:** `src/config.rs`
- Add DATABASE_MAX_CONNECTIONS (default: 5)

## Environment Variables

```bash
DATABASE_URL=postgres://guruconnect:password@localhost:5432/guruconnect
DATABASE_MAX_CONNECTIONS=5
```

## Key Design Decisions

1. **Optional database** - Server works without DB if DATABASE_URL not set
2. **Hybrid storage** - In-memory for real-time, DB for persistence
3. **Prefix tables** - `connect_*` to coexist with GuruRMM
4. **Fire-and-forget logging** - DB writes don't block WebSocket handling
5. **Restore on startup** - Persistent machines appear offline until reconnect

## Critical Files to Modify

| File | Changes |
|------|---------|
| `server/migrations/001_initial_schema.sql` | CREATE (new) |
| `server/src/db/mod.rs` | Expand with pool + submodules |
| `server/src/db/machines.rs` | CREATE (new) |
| `server/src/db/sessions.rs` | CREATE (new) |
| `server/src/db/events.rs` | CREATE (new) |
| `server/src/db/support_codes.rs` | CREATE (new) |
| `server/src/main.rs` | Add DB init, restore logic |
| `server/src/session/mod.rs` | Add restore_offline_machine |
| `server/src/relay/mod.rs` | Add event logging |
| `server/src/support_codes.rs` | Add DB methods |
| `server/src/config.rs` | Add pool size config |

## Testing

1. Start server with DATABASE_URL set
2. Connect agent - verify machine appears in connect_machines
3. Restart server - verify machine shows as offline
4. Reconnect agent - verify machine shows online
5. Connect viewer - verify viewer_joined event logged
6. Disconnect viewer - verify viewer_left event logged
7. Check connect_session_events for audit trail


The plan is complete. Let me review it and exit plan mode. The plan covers:

1. Why a service is needed (SYSTEM privileges required for SendSAS)
2. Architecture diagram showing agent → named pipe → service flow
3. Implementation steps:
   - Create SAS service binary
   - Create named pipe client in agent
   - Update keyboard.rs to use the client
   - Update startup.rs for service installation
   - Cargo.toml changes

This is a solid plan. Let me exit plan mode.


The plan is complete. It covers:

1. **Why a service is required** - SYSTEM privileges needed for SendSAS API
2. **Architecture** - Agent communicates with service via named pipe IPC
3. **Implementation steps** - 6 clear steps with code examples
4. **Files to create/modify** - 5 files total
5. **Security considerations** - Pipe DACL, command validation
6. **Installation flow** - Auto-install when agent starts


ExitPlanMode

# GuruConnect SAS Service Plan

## Overview

Create a Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence) requests. The agent communicates with the service via named pipe IPC.

## Why a Service is Required

- `SendSAS()` API requires SYSTEM privileges or `SeTcbPrivilege`
- Running as Administrator is NOT sufficient
- Windows blocks Ctrl+Alt+Del key injection for security
- All professional remote desktop tools (ScreenConnect, TeamViewer) use this pattern

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect Agent (runs as logged-in user)                 │
│  - Receives SpecialKey message from server                  │
│  - Connects to named pipe: \\.\pipe\guruconnect-sas         │
│  - Sends "sas" command                                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect SAS Service (runs as SYSTEM)                   │
│  - Windows Service: "GuruConnectSAS"                        │
│  - Listens on named pipe                                    │
│  - Calls SendSAS(0) via sas.dll                             │
│  - Returns success/error                                    │
└─────────────────────────────────────────────────────────────┘
```

## Implementation

### New Binary: `guruconnect-sas-service`

**Location:** `agent/src/bin/sas_service.rs` (or separate crate)

**Functionality:**
1. Windows Service using `windows-service` crate (already in Cargo.toml)
2. Named pipe server at `\\.\pipe\guruconnect-sas`
3. Accepts commands: `sas` (send Ctrl+Alt+Del)
4. Loads `sas.dll` and calls `SendSAS(0)`

### Service Details

```rust
// Service constants
const SERVICE_NAME: &str = "GuruConnectSAS";
const SERVICE_DISPLAY: &str = "GuruConnect SAS Service";
const SERVICE_DESC: &str = "Handles Secure Attention Sequence for GuruConnect remote sessions";
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";

// Install location
const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";
```

### Files to Create/Modify

| File | Action | Purpose |
|------|--------|---------|
| `agent/src/bin/sas_service.rs` | CREATE | Windows Service binary |
| `agent/src/sas_client.rs` | CREATE | Named pipe client for agent |
| `agent/src/input/keyboard.rs` | MODIFY | Use sas_client instead of direct call |
| `agent/src/startup.rs` | MODIFY | Install/uninstall service |
| `agent/Cargo.toml` | MODIFY | Add binary target |

### Step-by-Step Implementation

#### Step 1: Create SAS Service Binary (`sas_service.rs`)

```rust
// Key components:
// 1. Windows service boilerplate (define_windows_service!, service_dispatcher)
// 2. Named pipe server loop
// 3. SendSAS implementation
// 4. Install/uninstall subcommands

fn main() {
    match std::env::args().nth(1).as_deref() {
        Some("install") => install_service(),
        Some("uninstall") => uninstall_service(),
        Some("run") => run_service(),  // Called by SCM
        _ => print_usage(),
    }
}
```

#### Step 2: Named Pipe Server

```rust
// Create pipe with SYSTEM security (allow all users to connect)
let pipe = CreateNamedPipeW(
    PIPE_NAME,
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    1,  // Max instances
    512, 512,  // Buffer sizes
    0,  // Default timeout
    Some(&security_attributes),  // Allow non-admin connections
);

loop {
    ConnectNamedPipe(pipe, None);
    // Read command, execute, respond
    DisconnectNamedPipe(pipe);
}
```

#### Step 3: SendSAS Implementation

```rust
fn send_sas() -> Result<()> {
    unsafe {
        let lib = LoadLibraryW(w!("sas.dll"))?;
        let proc = GetProcAddress(lib, s!("SendSAS"))
            .ok_or_else(|| anyhow!("SendSAS not found"))?;
        let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
        send_sas(0);  // 0 = Ctrl+Alt+Del
        Ok(())
    }
}
```

#### Step 4: Agent SAS Client (`sas_client.rs`)

```rust
pub fn request_sas() -> Result<()> {
    let mut pipe = File::options()
        .read(true)
        .write(true)
        .open(r"\\.\pipe\guruconnect-sas")?;

    pipe.write_all(b"sas\n")?;

    let mut response = [0u8; 64];
    let n = pipe.read(&mut response)?;

    if &response[..n] == b"ok\n" {
        Ok(())
    } else {
        Err(anyhow!("SAS service error"))
    }
}
```

#### Step 5: Update keyboard.rs

```rust
pub fn send_sas(&mut self) -> Result<()> {
    // Try service first
    if let Ok(()) = crate::sas_client::request_sas() {
        return Ok(());
    }

    // Fallback to direct call (for elevated sessions)
    // ... existing code ...
}
```

#### Step 6: Service Installation (startup.rs)

```rust
pub fn install_sas_service() -> Result<()> {
    // Copy sas_service.exe to C:\Program Files\GuruConnect\
    // Run: sc create GuruConnectSAS binPath= "..." start= auto
    // Run: sc start GuruConnectSAS
}

pub fn uninstall_sas_service() -> Result<()> {
    // Run: sc stop GuruConnectSAS
    // Run: sc delete GuruConnectSAS
    // Remove exe file
}
```

### Cargo.toml Changes

```toml
[[bin]]
name = "guruconnect-sas-service"
path = "src/bin/sas_service.rs"

[dependencies]
# Already present:
windows-service = "0.7"
windows = { version = "0.58", features = [...] }
```

### Security Considerations

1. **Pipe Security:** Create pipe with DACL allowing authenticated users
2. **Service Account:** Runs as LocalSystem (has SeTcbPrivilege)
3. **Command Validation:** Only accept "sas" command, nothing else
4. **No Sensitive Data:** Pipe only carries simple commands

### Installation Flow

1. Agent starts (persistent mode)
2. Checks if SAS service installed
3. If not, copies service exe and installs via `sc.exe`
4. Starts service
5. Future Ctrl+Alt+Del requests go through pipe

### Testing

1. Build both binaries
2. Install service: `guruconnect-sas-service.exe install`
3. Connect agent, join viewer
4. Click Ctrl+Alt+Del button
5. Verify Windows security screen appears

### Reference Files

- GuruRMM service pattern: `gururmm/agent/src/service.rs`
- Current keyboard: `guru-connect/agent/src/input/keyboard.rs`
- Named pipe examples: Windows API documentation


User has approved your plan. You can now start coding. Start with updating your todo list if applicable

Your plan has been saved to: C:\Users\MikeSwanson\.claude\plans\velvet-tumbling-tide.md
You can refer back to it if needed during implementation.

## Approved Plan:
# GuruConnect SAS Service Plan

## Overview

Create a Windows Service running as SYSTEM to handle Ctrl+Alt+Del (Secure Attention Sequence) requests. The agent communicates with the service via named pipe IPC.

## Why a Service is Required

- `SendSAS()` API requires SYSTEM privileges or `SeTcbPrivilege`
- Running as Administrator is NOT sufficient
- Windows blocks Ctrl+Alt+Del key injection for security
- All professional remote desktop tools (ScreenConnect, TeamViewer) use this pattern

## Architecture

```
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect Agent (runs as logged-in user)                 │
│  - Receives SpecialKey message from server                  │
│  - Connects to named pipe: \\.\pipe\guruconnect-sas         │
│  - Sends "sas" command                                      │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│  GuruConnect SAS Service (runs as SYSTEM)                   │
│  - Windows Service: "GuruConnectSAS"                        │
│  - Listens on named pipe                                    │
│  - Calls SendSAS(0) via sas.dll                             │
│  - Returns success/error                                    │
└─────────────────────────────────────────────────────────────┘
```

## Implementation

### New Binary: `guruconnect-sas-service`

**Location:** `agent/src/bin/sas_service.rs` (or separate crate)

**Functionality:**
1. Windows Service using `windows-service` crate (already in Cargo.toml)
2. Named pipe server at `\\.\pipe\guruconnect-sas`
3. Accepts commands: `sas` (send Ctrl+Alt+Del)
4. Loads `sas.dll` and calls `SendSAS(0)`

### Service Details

```rust
// Service constants
const SERVICE_NAME: &str = "GuruConnectSAS";
const SERVICE_DISPLAY: &str = "GuruConnect SAS Service";
const SERVICE_DESC: &str = "Handles Secure Attention Sequence for GuruConnect remote sessions";
const PIPE_NAME: &str = r"\\.\pipe\guruconnect-sas";

// Install location
const INSTALL_DIR: &str = r"C:\Program Files\GuruConnect";
```

### Files to Create/Modify

| File | Action | Purpose |
|------|--------|---------|
| `agent/src/bin/sas_service.rs` | CREATE | Windows Service binary |
| `agent/src/sas_client.rs` | CREATE | Named pipe client for agent |
| `agent/src/input/keyboard.rs` | MODIFY | Use sas_client instead of direct call |
| `agent/src/startup.rs` | MODIFY | Install/uninstall service |
| `agent/Cargo.toml` | MODIFY | Add binary target |

### Step-by-Step Implementation

#### Step 1: Create SAS Service Binary (`sas_service.rs`)

```rust
// Key components:
// 1. Windows service boilerplate (define_windows_service!, service_dispatcher)
// 2. Named pipe server loop
// 3. SendSAS implementation
// 4. Install/uninstall subcommands

fn main() {
    match std::env::args().nth(1).as_deref() {
        Some("install") => install_service(),
        Some("uninstall") => uninstall_service(),
        Some("run") => run_service(),  // Called by SCM
        _ => print_usage(),
    }
}
```

#### Step 2: Named Pipe Server

```rust
// Create pipe with SYSTEM security (allow all users to connect)
let pipe = CreateNamedPipeW(
    PIPE_NAME,
    PIPE_ACCESS_DUPLEX,
    PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
    1,  // Max instances
    512, 512,  // Buffer sizes
    0,  // Default timeout
    Some(&security_attributes),  // Allow non-admin connections
);

loop {
    ConnectNamedPipe(pipe, None);
    // Read command, execute, respond
    DisconnectNamedPipe(pipe);
}
```

#### Step 3: SendSAS Implementation

```rust
fn send_sas() -> Result<()> {
    unsafe {
        let lib = LoadLibraryW(w!("sas.dll"))?;
        let proc = GetProcAddress(lib, s!("SendSAS"))
            .ok_or_else(|| anyhow!("SendSAS not found"))?;
        let send_sas: extern "system" fn(i32) = std::mem::transmute(proc);
        send_sas(0);  // 0 = Ctrl+Alt+Del
        Ok(())
    }
}
```

#### Step 4: Agent SAS Client (`sas_client.rs`)

```rust
pub fn request_sas() -> Result<()> {
    let mut pipe = File::options()
        .read(true)
        .write(true)
        .open(r"\\.\pipe\guruconnect-sas")?;

    pipe.write_all(b"sas\n")?;

    let mut response = [0u8; 64];
    let n = pipe.read(&mut response)?;

    if &response[..n] == b"ok\n" {
        Ok(())
    } else {
        Err(anyhow!("SAS service error"))
    }
}
```

#### Step 5: Update keyboard.rs

```rust
pub fn send_sas(&mut self) -> Result<()> {
    // Try service first
    if let Ok(()) = crate::sas_client::request_sas() {
        return Ok(());
    }

    // Fallback to direct call (for elevated sessions)
    // ... existing code ...
}
```

#### Step 6: Service Installation (startup.rs)

```rust
pub fn install_sas_service() -> Result<()> {
    // Copy sas_service.exe to C:\Program Files\GuruConnect\
    // Run: sc create GuruConnectSAS binPath= "..." start= auto
    // Run: sc start GuruConnectSAS
}

pub fn uninstall_sas_service() -> Result<()> {
    // Run: sc stop GuruConnectSAS
    // Run: sc delete GuruConnectSAS
    // Remove exe file
}
```

### Cargo.toml Changes

```toml
[[bin]]
name = "guruconnect-sas-service"
path = "src/bin/sas_service.rs"

[dependencies]
# Already present:
windows-service = "0.7"
windows = { version = "0.58", features = [...] }
```

### Security Considerations

1. **Pipe Security:** Create pipe with DACL allowing authenticated users
2. **Service Account:** Runs as LocalSystem (has SeTcbPrivilege)
3. **Command Validation:** Only accept "sas" command, nothing else
4. **No Sensitive Data:** Pipe only carries simple commands

### Installation Flow

1. Agent starts (persistent mode)
2. Checks if SAS service installed
3. If not, copies service exe and installs via `sc.exe`
4. Starts service
5. Future Ctrl+Alt+Del requests go through pipe

### Testing

1. Build both binaries
2. Install service: `guruconnect-sas-service.exe install`
3. Connect agent, join viewer
4. Click Ctrl+Alt+Del button
5. Verify Windows security screen appears

### Reference Files

- GuruRMM service pattern: `gururmm/agent/src/service.rs`
- Current keyboard: `guru-connect/agent/src/input/keyboard.rs`
- Named pipe examples: Windows API documentation


The plan is approved. Let me now implement the SAS service. I'll start by:

1. Creating the todo list
2. Creating the SAS service binary
3. Creating the SAS client module
4. Updating keyboard.rs
5. Updating startup.rs for service installation
6. Updating Cargo.toml

Let me start with the todo list and then implement each component.


TodoWrite

Create SAS service binary (sas_service.rs)

Create SAS client module (sas_client.rs)

Update keyboard.rs to use SAS client

Update startup.rs for service install/uninstall

Update Cargo.toml with binary target

Build and test


Todos have been modified successfully. Ensure that you continue to use the todo list to track your progress. Please proceed with the current tasks if applicable

Create SAS service binary (sas_service.rs)

Create SAS client module (sas_client.rs)

Update keyboard.rs to use SAS client

Update startup.rs for service install/uninstall

Update Cargo.toml with binary target

Build and test


Let me first read the GuruRMM service.rs to understand the patterns, then create the SAS service.