# Native Remote Control — Code References > Two repos. **GC** = guru-connect (`D:\claudetools\projects\msp-tools\guru-connect`, lives > in the claudetools repo). **RMM** = GuruRMM (`projects/msp-tools/guru-rmm`, a git submodule > tracking `azcomputerguru/gururmm`). Paths below are relative to each repo root. ## Files that will be touched ### guru-connect (GC) - `server/src/main.rs` — route table; `create_code` `:382`, `list_sessions` `:425`, `get_session` `:433`, `list_machines` `:467`, `/health` `:254`, public `/api/version` `:300`. **Add** the `/api/integration/v1/` namespace: `GET .../capabilities`, `POST .../sessions`, `POST .../sessions/:id/viewer-token`, `POST .../agents/:agent_id/keys`; register the server-to-server integration auth layer. Model the (unauthenticated) capabilities endpoint on the existing `/api/version` route. - `CONTRACT.md` (new — GC repo root or `docs/`) — the semver'd integration contract doc both teams keep front of mind. Source of truth for the surface; tested in CI (Task 11). - `server/src/api/releases.rs:76` — `GET /api/version` handler (no auth, for agent polling). Pattern to model `GET /api/integration/v1/capabilities` on. - `server/static/viewer.html` — the existing **web viewer**; gets an `?embed=1` mode (hide standalone chrome, accept host-provided session/token, emit `postMessage` lifecycle events for the RMM host). - `server/src/middleware/security_headers.rs:30` (`frame-ancestors 'none'`) and `:37-39` (`X-Frame-Options`) — **the embedding blocker.** Add a per-route scoped allowlist for the viewer path only (RMM origin from env); leave every other route at `'none'`. - `server/src/session/mod.rs` — in-memory `SessionManager`; `register_agent()` `:95`, `join_session()` `:254`. **Change** to allow a session to be pre-created/keyed by `agent_id` before the agent connects, then bound when the agent registers. - `server/src/db/sessions.rs` — `create_session()` `:22`. **Change/add** to persist pre-created sessions and a `is_managed`/`source` marker; reconcile in-memory state on startup. - `server/src/db/support_codes.rs` — `create_support_code()` `:24`, `get_support_code()` `:43`. Reused as-is for the attended path (broker calls `POST /api/codes`). - `server/src/relay/mod.rs` — agent WS handler `:55`/`:236`; `validate_agent_api_key()` `:187` (currently JWT-or-shared-`AGENT_API_KEY`, comment at `:200` flags DB keys as future). **Change** to validate against the new per-machine key table. - `server/src/auth/jwt.rs` — JWT signing/validation. **Add** a short-lived, session-scoped viewer token mint. - `server/migrations/` — **add** `connect_agent_keys` (per-machine keys) and session columns; follow the existing `001_initial_schema.sql` / `003_auto_update.sql` style. Idempotent (`IF NOT EXISTS`). - `proto/guruconnect.proto` — `SessionRequest` `:8`, `StartStream` `:261`, `AgentStatus` `:271`, `AdminCommand` `:286`. **Add** `ConsentRequest` / `ConsentResponse` messages. - `agent/src/session/mod.rs` — `SessionState` `:71`, persistent-vs-support logic. **Change** to register against a broker-assigned `agent_id` (= GuruRMM `device_id`). - `agent/src/transport/websocket.rs` — `connect()` `:32` (builds `?agent_id=&api_key=&support_code=`). Pass the per-machine key. - `agent/src/tray/mod.rs` + a new consent dialog — **add** the attended-mode consent prompt (handle `ConsentRequest`). - `agent/src/install.rs` — `register_protocol_handler()` `:131` (`guruconnect://?token=&server=`). Reused for native-viewer launch URLs the broker returns. ### GuruRMM (RMM) - `server/src/api/commands.rs:87-157` — `POST /api/agents/{agent_id}/command` dispatch (online → WS `ServerMessage::Command`; offline → queued). **Reuse** to push the "ensure + launch guru-connect" instruction to the endpoint agent. - `server/src/api/mod.rs:162` — route registration site. **Add** the new broker route. - `server/src/api/` — **add** `remote_control.rs`: `POST /api/agents/:agent_id/remote-control` (body selects `unattended|attended`); talks to the GC server API, returns a viewer launch URL. - `server/src/db/` + `server/migrations/` — **add** a `remote_control_sessions` record (or reuse `tech_sessions` from `010_tunnel_sessions.sql`) for audit (`agent_id`, `tech_id`, `connect_session_id`, `mode`, timestamps). - `agent/src/transport/websocket.rs` — `run_command()` `:1050`, `execute_command()` `:971`. **Add** a `RemoteControl`/launch path (or a dedicated command_type) that, on Windows, ensures the guru-connect agent binary is present (download + SHA-256 verify) and launches it in the requested mode passing `device_id` as the GC `agent_id`. - `agent/src/device_id.rs:1-99` — source of the stable cross-product identity. Read-only. - `dashboard/src/pages/AgentDetail.tsx:1893-1931` — tab/header + action-button area. **Add** the "Remote Control" button (open viewer URL on success). - `dashboard/src/components/CommandTerminal.tsx:60-106` — the canonical button→`api.post()`→`useQuery` action pattern to copy. - `dashboard/src/api/client.ts:293-310` — `commandsApi` pattern. **Add** `remoteControlApi.start(agentId, mode)`. ## Similar existing implementations (patterns to follow) - **Per-agent action dispatch (RMM):** `server/src/api/commands.rs:87-157` + agent reception `agent/src/transport/websocket.rs:570-573` → `execute_command()` `:971` → `run_command()` `:1050`. The broker's "launch guru-connect" instruction follows this exact send-command path. - **Dashboard action button → poll (RMM):** `dashboard/src/components/CommandTerminal.tsx:82-105` (`useMutation` → `commandsApi.send` → `useQuery` poll). The Remote Control button mirrors this. - **Per-agent credential issuance (RMM):** `server/src/api/enroll.rs:38-139` — `generate_api_key("agk_")` `:103`, `hash_api_key()` `:104`, plaintext returned once `:138`. Model `connect_agent_keys` provisioning on this. - **Support-code minting (GC):** `server/src/main.rs:382` `create_code` + `server/src/db/support_codes.rs:24`. The attended path reuses this directly. - **Agent WS auth handshake (RMM):** `agent/src/transport/websocket.rs:100-197` — how api_key/device_id are presented; the per-machine GC key provisioning should align with this lifecycle. - **Half-built generic tunnel (RMM), for reference only:** server `server/src/api/tunnel.rs:1-232` (routes NOT registered), `server/src/db/tunnel.rs:1-152`, `server/migrations/010_tunnel_sessions.sql`, agent `agent/src/tunnel/mod.rs:62-197`, WS msgs `server/src/ws/mod.rs:287-300`. The `tech_sessions`/`tunnel_audit` schema is a usable model for the remote-control audit record. ## Database schema ### guru-connect (existing — `server/migrations/`) - `connect_machines` (`001_initial_schema.sql:8`) — `agent_id` UNIQUE, `hostname`, `is_persistent`, `status`, plus `agent_version`/`organization`/`site`/`tags` from `003_auto_update.sql`. - `connect_sessions` (`001_initial_schema.sql:27`) — `id`, `machine_id`, `is_support_session`, `support_code`, `status`. **Add** `is_managed` / `source` marker for broker-initiated sessions. - `connect_support_codes` (`001_initial_schema.sql:59`) — reused unchanged for attended. - `connect_session_events` (`001_initial_schema.sql:43`) — audit; emit broker/consent events here. - `releases` (`003_auto_update.sql:9`) — has `checksum_sha256`; reuse for the verify-before-launch supply-chain guard. - **New:** `connect_agent_keys` — `id`, `agent_id` FK, `key_hash`, `created_at`, `revoked_at`. Idempotent migration, hashed keys only (mirror RMM enroll pattern). ### GuruRMM (existing — `server/migrations/`) - Agent identity: `agent_id` (UUID, assigned at WS auth), `device_id` (`agent/src/device_id.rs`), `site_id`, per-agent `agk_` key (hashed) from `server/src/api/enroll.rs`. - `tech_sessions` / `tunnel_audit` (`010_tunnel_sessions.sql`) — model for the new `remote_control_sessions` audit table (or extend `tech_sessions` with a `mode`). > Migration discipline for both Rust servers: idempotent `IF NOT EXISTS`, let the server binary > apply migrations on startup, `cargo sqlx prepare` if any `query!()` macro changes. See > `gururmm/sqlx-migrations` standard.