Skip to content

discovery detailed analysis

Pola, Sudhir edited this page Apr 27, 2026 · 3 revisions

DMT Device Discovery & Observability — Solution Proposal

Objective

This document proposes a unified solution for three related features in the Device Management Toolkit (DMT). The common goal is to give operators greater visibility into their AMT-capable device fleet — covering what devices are present on the network, what their firmware and platform state is, and whether key services such as Intel LMS are running. The proposal defines the required data model changes, API impact across Console, MPS, RPS, and rpc-go, and a design for the discovery agent that collects and reports device state.

Scope

Issue Summary
sample-web-ui#1265SKU Type & Config Mode in Devices UI Display SKU type (vPro / ISM / Non-vPro) and Configuration mode (ACM / CCM / Pre-Prov) in the Devices list of the OpenAMT UI. Use the existing deviceInfo JSON column in MPS; add the same column to Console if not already present.
console#858Device Discovery Add a discovery capability so customers gain visibility into AMT-capable devices across their network. A lightweight agent (likely extending rpc-go) collects AMT/ME details (FW version, SKU, control mode, TLS mode, network config, UPID, cert hashes) and platform details (OS, LMS, CPU, hostname, IP, ethernet adapters, monitor presence) and reports them back to Console. Console ingests, stores, and visualises the data via a dashboard with filtering, sorting, capability breakdowns, and export support.
rpc-go#1246Detect LMS During Orchestration Detect whether Intel LMS (Local Manageability Service) is installed on a device during the v3 orchestration/registration process. rpc-go reports LMS presence in the registration payload; Console captures and persists it in a new is_lms_available field in the device information table. UI enhancements based on LMS presence are out of scope for this story.

Solution Proposal

Proposed Data Model Changes

Fields Required by the Three Features — Gap Analysis

The table below maps each data field required by the three issues against what is currently stored.

Required Field Required By Currently in MPS/Console? Notes
CSME FW Version Discovery, SKU/Mode UI deviceInfo.fwVersion Already stored in deviceInfo JSON
SKU type label (vPro / ISM / Non-vPro) Discovery, SKU/Mode UI ⚠️ Partial — raw deviceInfo.fwSku string SKU is stored as a raw number; decoded label (features field) exists but is not a structured enum
Control Mode (ACM / CCM / Pre-Prov) Discovery, SKU/Mode UI deviceInfo.currentMode Stored as "0"/"1"/"2"; UI label decoding needed
Provisioned State Discovery ✅ Derivable from currentMode 0 = pre-provisioned, 1/2 = provisioned
TLS Mode Discovery ❌ Not stored Not in deviceInfo or any column
DHCP vs Static Discovery ⚠️ Partial — deviceInfo.ipAddress only No DHCP flag, gateway, or subnet stored
802.1x / wireless config Discovery ❌ Not stored Available via Console AMT API (/amt/networkSettings) but not persisted
AMT certificate hashes Discovery ❌ Not stored Available during activation payload but not persisted in deviceInfo
UPID Discovery ❌ Not stored Not collected anywhere today
AMT enabled in BIOS Discovery ❌ Not stored Collectable via rpc-go but not stored
ME Interface Version Discovery ❌ Not stored Not in deviceInfo
LMS version / installed Discovery, LMS Orchestration ❌ Not stored Not collected today; #1246 requires detection at registration time — stored as lmsInstalled in discoveryInfo (partial write at registration; enriched with lmsVersion during a full discovery scan)
OS details (distro, version) Discovery ❌ Not stored Not collected today
CPU details Discovery ❌ Not stored Available via Console CIM API (/amt/hardwareInfo) but not persisted
OS IP address Discovery ⚠️ Partial — deviceInfo.ipAddress is AMT IP OS IP may differ from AMT IP
Number of ethernet adapters Discovery ❌ Not stored Not collected today
Monitor connected Discovery ❌ Not stored Not collected today
OS hostname Discovery hostname column Already a top-level column
DNS suffix (AMT) Discovery dnsSuffix column Already a top-level column
Last reported/discovered time Discovery (NFR) deviceInfo.lastUpdated Exists in deviceInfo JSON
Export support Discovery (NFR) ⚠️ Partial Console has an export usecase but not wired for discovery data

Summary

  • MPS is the system of record for CIRA connection state and the deviceInfo JSON blob. It does not store credentials.
  • Console mirrors deviceInfo, adds credentials/TLS fields, and is the entry point for all AMT management actions. It has live APIs to query most of the missing fields (network, hardware, features) but does not persist them back to the database.
  • The deviceInfo JSON column is the natural extension point for adding new discovery fields without a disruptive schema change. Fields that are queried live today (network settings, hardware info, features) should be persisted into this blob (or a new discoveryInfo column) by the discovery agent on each report cycle.
  • Fields not yet collected anywhere (UPID, LMS version, OS distro, monitor detection, 802.1x state) require new collection logic — AMT-level fields (UPID) via rpc amtinfo --sync in rpc-go; OS-level fields (LMS, OS distro, monitor, 802.1x) via the rpc-discovery OS collector on the device.

The three features require extending the existing devices table with additional persisted data. The proposal is to extend the existing deviceInfo JSON column for fields that relate to the managed/activated device, and introduce a new discoveryInfo JSON column for fields that are relevant only in the discovery context (OS-level data, platform checks, LMS state). This avoids a disruptive schema migration while keeping the two concerns clearly separated.

Storage Decision Summary

Concern Storage Location Rationale
AMT/ME firmware state (SKU, mode, TLS, UPID) Extend deviceInfo JSON — both MPS and Console Consistent with today's write path; RPS and rpc amtinfo --sync already populate this blob
OS and platform state (LMS, OS details, network adapters) New discoveryInfo JSON column — Console only Discovery-specific; not relevant for managed-device operations; Console is the primary consumer
LMS presence at registration (lmsInstalled) discoveryInfo JSON — Console only, partial write at registration Consolidates all LMS state; discoveryInfo written partially at registration, enriched fully at discovery scan
Credentials and TLS connection config Existing Console-only columns — no change Already correctly separated
Live connection state (connectionStatus, lastConnected) Existing MPS columns — no change Updated at CIRA runtime; not a discovery concern

Entity Structure

The diagram below shows the current entity structure and the proposed additions. Each field is annotated with its presence — M = MPS, C = Console, M+C = both — and new fields are marked with <<new>>.

erDiagram
    DEVICES {
        uuid guid PK "M+C"
        string hostname "M+C"
        string dnsSuffix "M+C"
        boolean connectionStatus "M+C"
        string mpsusername "M+C"
        string mpsinstance "M+C"
        string tenantId "M+C"
        string friendlyName "M+C"
        string tags "M+C"
        timestamp lastConnected "M+C"
        timestamp lastSeen "M+C"
        timestamp lastDisconnected "M+C"
        json deviceInfo "M+C"
        json discoveryInfo "C <<new>>"
    }

    DEVICE_INFO {
        string fwVersion "M+C"
        string fwBuild "M+C"
        string fwSku "M+C"
        string currentMode "M+C"
        string features "M+C"
        string ipAddress "M+C"
        timestamp lastUpdated "M+C"
        string tlsMode "M+C <<new>>"
        string upid "M+C <<new>>"
        boolean amtEnabledInBIOS "M+C <<new>>"
        string meInterfaceVersion "M+C <<new>>"
        boolean dhcpEnabled "M+C <<new>>"
        string[] certHashes "M+C <<new>>"
    }

    DISCOVERY_INFO {
        string lmsVersion "C <<new>>"
        boolean lmsInstalled "C <<new>>"
        string osName "C <<new>>"
        string osVersion "C <<new>>"
        string osDistro "C <<new>>"
        string cpuModel "C <<new>>"
        string osIpAddress "C <<new>>"
        int ethernetAdapterCount "C <<new>>"
        boolean monitorConnected "C <<new>>"
        boolean ieee8021xEnabled "C <<new>>"
        timestamp lastDiscovered "C <<new>>"
    }

    DEVICES ||--|| DEVICE_INFO : "deviceInfo (JSON)"
    DEVICES ||--|| DISCOVERY_INFO : "discoveryInfo (JSON)"
Loading

Proposed Extensions to deviceInfo JSON

These fields relate to the AMT/ME state of an already-managed or activatable device. They extend the existing deviceInfo blob, keeping the same write path (RPS activation + rpc amtinfo --sync).

Field Type Feature Collection Method Presence
tlsMode string Discovery AMT_TLSProtocolEndpointCollection via WSMAN (/amt/tls/:guid) M + C
upid string Discovery AMT_SetupAndConfigurationService.GetUuid — already in rpc-go InfoResult M + C
amtEnabledInBIOS boolean Discovery AMT_BootSettingData via WSMAN or rpc-go amtinfo M + C
meInterfaceVersion string Discovery CIM_SoftwareIdentity (ME version) — via GET /amt/version/:guid in Console M + C
dhcpEnabled boolean Discovery AMT_EthernetPortSettings.DHCPEnabled — already in GET /amt/networkSettings/:guid M + C
certHashes string[] Discovery Sent in rpc-go activation payload (MessagePayload.CertificateHashes) — already collected, not persisted M + C

Key: M = MPS · C = Console · M + C = both

Proposed New discoveryInfo JSON Column

These fields describe the OS environment and platform state rather than AMT configuration. They are only meaningful in the discovery context and do not belong in the managed-device deviceInfo blob.

Key: for Presence M = MPS · C = Console · M + C = both

Field Type Feature Collection Method Presence
lmsVersion string Discovery, LMS Orchestration rpc-discovery OS collector — OS package query or LMS API C
lmsInstalled boolean Discovery, LMS Orchestration rpc-discovery OS collector — check process/service presence; partial write at registration (#1246), enriched at discovery scan C
osName string Discovery rpc-discovery OS collector — runtime.GOOS (no rpc needed) C
osVersion string Discovery rpc-discovery OS collector — OS version API (no rpc needed) C
osDistro string Discovery rpc-discovery OS collector — Linux: /etc/os-release; Windows: WMI (no rpc needed) C
cpuModel string Discovery rpc-discovery OS collector — Linux: /proc/cpuinfo; Windows: WMI Win32_Processor (no WSMAN; works on non-AMT devices) C
osIpAddress string Discovery rpc-discovery OS collector — net.InterfaceAddrs() (no rpc needed) C
ethernetAdapterCount int Discovery rpc-discovery OS collector — net.Interfaces() (no rpc needed) C
monitorConnected boolean Discovery rpc-discovery OS collector — Windows: EnumDisplayMonitors; Linux: EDID/DRM check (no rpc needed) C
ieee8021xEnabled boolean Discovery rpc-discovery OS collector — Linux: nmcli/wpa_supplicant config; Windows: WMI Win32_NetworkAdapterConfiguration (no WSMAN; works on non-AMT devices) C
lastDiscovered timestamp Discovery (NFR) Set by rpc-discovery agent on each report cycle C

LMS Availability — Storage Placement Decision

rpc-go#1246 explicitly calls for storing LMS presence in the "device information table". However, this requires a deliberate placement decision given the two-blob design.

Arguments for deviceInfo

  • The issue author explicitly targets the device information table.
  • The write event is registration time (v3 orchestration), not a discovery scan cycle — a device that is registered but never scanned would otherwise have no LMS data.

Arguments for discoveryInfo

  • LMS is an OS-level service, not an AMT/ME firmware concept. The design principle is: AMT/ME state → deviceInfo, OS/platform state → discoveryInfo.
  • discoveryInfo already defines lmsInstalled (boolean) and lmsVersion (string). Adding isLmsAvailable to deviceInfo splits LMS data across two blobs.
  • Consolidating all LMS state in one location simplifies queries and UI display.

Decision: discoveryInfo with partial writes at registration

discoveryInfo is the correct home. The write path for discoveryInfo must be extended to accept partial writes at registration time, not only during full discovery scans. Concretely:

  • The boolean from rpc-go#1246 maps to the existing lmsInstalled field in discoveryInfo — no new field is needed.
  • The Console registration handler writes just lmsInstalled into discoveryInfo when the device registers, leaving all other fields null until a discovery scan runs.
  • A subsequent discovery scan enriches the same blob with lmsVersion, osName, and the remaining platform fields.

This avoids splitting LMS data, respects the design principle, and ensures LMS presence is captured immediately at registration even for devices that never undergo a full discovery scan.

API Impact Analysis

This section maps the existing API surface against the three features, identifying which endpoints need modification and which new endpoints must be introduced.

Registration and Deregistration Flows

rpc-go already supports two mechanisms for managing a device's presence in Console. The three new features extend both paths.

Path A — Local activation (direct to Console as part of next branch):

rpc activate --local --profile <profile.yaml> \
             --auth-endpoint <console>/api/v1/authorize \
             --auth-username <user> --auth-password <pass>
  1. Authenticate with Console: POST /api/v1/authorize → JWT token
  2. Register device in Console before AMT orchestration: POST /api/v1/devices with a DevicePayload struct carrying GUID, hostname, AMT/MEBx/MPS credentials, and TLS configuration flags
  3. Run orchestrator (CCM/ACM activation, optional CIRA configuration)
  4. If CIRA configuration fails: clear MPS password via PATCH /api/v1/devices { "guid": "...", "mpspassword": "" }

Path A — Local deactivation (direct to Console as part of next branch):

rpc deactivate --local --auth-endpoint <console>/api/v1/authorize \
               --auth-username <user> --auth-password <pass>
  1. Resolve device GUID from AMT (before unprovisioning — AMT is unavailable afterwards)
  2. Locally unprovision AMT
  3. Delete device from Console: DELETE /api/v1/devices/{guid}

Path B — Remote activation via RPS (unchanged):

rpc activate -u wss://rps.example.com --profile <profile>

rpc-go sends a MessagePayload to RPS over WebSocket. This path does not call POST /api/v1/devices directly from rpc-go.

The new LMS detection (rpc-go#1246) must be propagated through both paths, and Console's registration handler must persist the flag into discoveryInfo.lmsInstalled.

API Change Summary

# Endpoint Service Type Feature
1 POST /api/v1/devices Console only Modify — accept isLMSAvailable in registration body LMS Orchestration
2 POST /api/v1/devices/discovery Console only New Discovery
3 GET /api/v1/devices[/:guid] Console only Modify — include discoveryInfo in response Discovery, SKU/Mode UI
4 GET /api/v1/devices Console only Modify — add filter/sort query params Discovery
5 GET /api/v1/devices/export Console only New Discovery
6 PATCH /api/v1/devices Console + MPS Modify — extend deviceInfo fields Discovery, SKU/Mode UI
7 PATCH /api/v1/devices Console only Modify — accept discoveryInfo partial payload Discovery, LMS Orchestration
8 rpc amtinfo --sync payload rpc-go Modify — extend syncDeviceInfo + add discoveryInfo Discovery
9 rpc-go registration payloads rpc-go Modify — add isLMSAvailable (Path A DevicePayload) + lmsInstalled (Path B MessagePayload) LMS Orchestration

Required API Changes

1. POST /api/v1/devices — Accept isLMSAvailable in registration body (Console — Modify)

Feature: LMS Orchestration (rpc-go#1246) Change: The existing device registration endpoint must accept a new isLMSAvailable boolean field in the request body and persist it as discoveryInfo.lmsInstalled (a partial write into the discoveryInfo column; all other discoveryInfo fields remain null until a discovery scan runs).

This is a Console-side change that receives the value sent by rpc-go Path A (local activation). The DevicePayload struct in rpc-go internal/device/api.go must be extended with the field:

IsLMSAvailable bool `json:"isLMSAvailable"`
JSON field Maps to Type Write event
isLMSAvailable discoveryInfo.lmsInstalled boolean Device registration via Path A

Console handler must read isLMSAvailable from the POST body and write { "lmsInstalled": <value> } as a partial upsert into the discoveryInfo column. No equivalent change is needed in MPS.


2. POST /api/v1/devices/discovery — New discovery report endpoint (Console — New)

Feature: Device Discovery Change: A dedicated endpoint for the discovery agent to submit a full discovery report for a device (identified by UUID or hostname). This is distinct from the PATCH /api/v1/devices used for device management, allowing cleaner separation of concerns and a different auth scope if needed.

Method Path Description
POST /api/v1/devices/discovery Accepts a full discovery payload; upserts discoveryInfo and merges new deviceInfo fields

Request body:

{
  "guid": "...",
  "deviceInfo": {
    "tlsMode": "...",
    "upid": "...",
    "amtEnabledInBIOS": true,
    "meInterfaceVersion": "...",
    "dhcpEnabled": true,
    "certHashes": ["..."]
  },
  "discoveryInfo": {
    "lmsInstalled": true,
    "lmsVersion": "2025.1.0",
    "osName": "Windows",
    "osVersion": "11",
    "osDistro": "Windows 11 Pro",
    "cpuModel": "Intel Core Ultra 7 165H",
    "osIpAddress": "192.168.1.100",
    "ethernetAdapterCount": 2,
    "monitorConnected": true,
    "ieee8021xEnabled": false,
    "lastDiscovered": "2026-04-24T10:00:00Z"
  }
}

3. GET /api/v1/devices and GET /api/v1/devices/:guid — Return discoveryInfo (Console — Modify)

Feature: Device Discovery, SKU/Mode UI Change: Add discoveryInfo to the response DTO alongside the existing deviceInfo. The field should be omitted (omitempty) when null.

MPS's equivalent GET /api/v1/devices endpoints are not affected — discoveryInfo is a Console-only column.


4. GET /api/v1/devices — Add filtering and sorting query parameters (Console — Modify)

Feature: Device Discovery Change: The discovery dashboard requires server-side filtering and sorting on the new fields. Extend the existing GET /api/v1/devices to support:

Parameter Example Purpose
filter[skuType] vPro Filter by decoded SKU label
filter[currentMode] ACM Filter by control mode
filter[lmsInstalled] true Filter by LMS presence
filter[osName] Windows Filter by OS
sort hostname, fwVersion Sort column
order asc / desc Sort direction

These map to new SQL query clauses against the deviceInfo and discoveryInfo JSON columns.


5. GET /api/v1/devices/export — New export endpoint (Console — New)

Feature: Device Discovery (NFR) Change: Add a new endpoint that returns all discovery data as a downloadable file (CSV or JSON).

Method Path Description
GET /api/v1/devices/export Returns all devices with deviceInfo + discoveryInfo as CSV/JSON

Query parameters: format=csv|json, same filter parameters as the list endpoint.


6. PATCH /api/v1/devices — Extend deviceInfo payload (Console — Modify)

Feature: Discovery, SKU/Mode UI Change: The request body's deviceInfo object must accept the new fields added to the deviceInfo blob. No new endpoint is needed — the existing PATCH is extended.

Fields to add to the accepted deviceInfo payload:

Field Type Populated By
tlsMode string rpc amtinfo --sync (reads AMT_TLSProtocolEndpointCollection)
upid string rpc amtinfo --sync (reads AMT_SetupAndConfigurationService.GetUuid)
amtEnabledInBIOS boolean rpc amtinfo --sync (reads AMT_BootSettingData)
meInterfaceVersion string rpc amtinfo --sync (reads CIM_SoftwareIdentity)
dhcpEnabled boolean rpc amtinfo --sync (reads AMT_EthernetPortSettings.DHCPEnabled)
certHashes string[] RPS activation payload (MessagePayload.CertificateHashes)

Console must deserialise these into the DeviceInfo struct and persist them into the deviceInfo JSON column. No schema migration is required — deviceInfo is already a TEXT/JSON column.

The rpc-go syncDeviceInfo struct and MessagePayload struct must also be extended to carry these fields.


7. PATCH /api/v1/devices — Accept discoveryInfo partial payload (Console — Modify)

Feature: Device Discovery, LMS Orchestration Change: The existing PATCH must additionally accept an optional discoveryInfo JSON object and merge it into the new discoveryInfo column (Console only). The merge must be additive — fields present in the payload overwrite the stored value; absent fields are left unchanged, enabling partial writes.

This endpoint handles post-registration write events only:

  • At amtinfo --sync: rpc-go can include a partial discoveryInfo object with any fields collected during a sync run.
  • At discovery scan (console#858): the discovery agent sends a full discoveryInfo object.

Note: The initial lmsInstalled write at device registration time goes via POST /api/v1/devices (item #1 above), not this PATCH. For Path B (remote via RPS), RPS forwards the value to Console through its own registration flow.

Fields accepted in discoveryInfo:

Field Type Write Event
lmsInstalled boolean amtinfo --sync + Discovery scan
lmsVersion string Discovery scan
osName string Discovery scan
osVersion string Discovery scan
osDistro string Discovery scan
cpuModel string Discovery scan
osIpAddress string Discovery scan
ethernetAdapterCount int Discovery scan
monitorConnected boolean Discovery scan
ieee8021xEnabled boolean Discovery scan
lastDiscovered timestamp Discovery scan

8. rpc-go — Extend amtinfo --sync payload (rpc-go — Modify)

Feature: Discovery, LMS Orchestration Change: Extend the syncDeviceInfo struct to include the new deviceInfo fields (tlsMode, upid, amtEnabledInBIOS, meInterfaceVersion, dhcpEnabled, certHashes) and add a new discoveryInfo struct carrying OS-level fields. The SyncDeviceInfo function collects and includes these in the PATCH body.


9. rpc-go — Registration payloads — Add LMS flag (rpc-go — Modify)

Feature: LMS Orchestration (rpc-go#1246) Change: rpc-go detects LMS presence via TCP probe to localhost:16992 (or :16993 for AMT 19+ with local TLS) before sending any registration payload. The LMS flag must be added to both registration paths:

Path A — Local activation (DevicePayload → Console): Add IsLMSAvailable bool json:"isLMSAvailable" to the DevicePayload struct in internal/device/api.go. Console receives it via POST /api/v1/devices and stores it as discoveryInfo.lmsInstalled (see item #1).

Path B — Remote activation (MessagePayload → RPS): Extend the MessagePayload struct in internal/rps/message.go to include LmsInstalled bool json:"lmsInstalled". RPS receives this over WebSocket and must forward it to Console when registering the device (RPS → Console write path).

Discovery Agent Analysis

1. Service Implementation Technologies

A discovery agent must run persistently in the background, survive reboots, and operate without a logged-in user session. The platform-native service mechanisms are:

Windows

Technology Description Considerations
Windows Service (SCM) Registered with the Service Control Manager; starts at boot, restarts on failure, runs as SYSTEM or a dedicated account Standard approach; well-supported by Go (golang.org/x/sys/windows/svc) and by frameworks such as kardianos/service
Task Scheduler Periodic task triggered on schedule or at login Simpler to deploy but less reliable; no automatic restart on failure
COM Out-of-Process Server Background activation through COM High complexity; not appropriate for a device agent

Recommended for Windows: Windows Service via the kardianos/service library, which provides a single cross-platform service registration API.

Ubuntu / Linux

Technology Description Considerations
systemd unit (.service) Standard on Ubuntu 18.04+; Type=simple or Type=notify; supports Restart=on-failure and WantedBy=multi-user.target Preferred; fully scriptable; integrates with journald for logging
SysV init script Legacy /etc/init.d/ scripts Only needed for very old distributions; Ubuntu ships systemd by default
cron / at Scheduled execution Periodic only; cannot run continuously; no failure recovery
Docker container Containerised agent with --restart=always Adds a Docker dependency; suitable for server deployments, not typical AMT-capable end devices

Recommended for Linux: systemd service unit, distributed as a .service file alongside the binary.


2. Discovery Agent Architecture — Approaches

Two architectural approaches are feasible. Both are written in Go (consistent with rpc-go) and produce a standalone binary.

Approach A — Standalone Go Agent (separate binary, shells out to the rpc binary)

A new, dedicated binary — e.g. rpc-discovery — that runs as a long-lived service. Rather than importing rpc-go packages as a library, it invokes the rpc binary as a subprocess to perform AMT data collection and device sync, and handles only the service harness, scheduling loop, and OS-level collection itself.

rpc-discovery (new binary)
  ├── service harness (kardianos/service)
  ├── scheduler (periodic scan interval)
  ├── AMT collector   → shells out: rpc amtinfo --all --json
  │                                 rpc amtinfo --sync --url <console>/api/v1/devices
  ├── OS collector    (new Go code: LMS probe, OS details, adapters, monitor)
  └── Console reporter → shells out or direct HTTP:
                         POST <console>/api/v1/devices/discovery  (discoveryInfo payload)

The agent locates the rpc binary either alongside itself in the same directory (co-deployed) or via a configurable path in agent-config.yaml.

Pros Cons
No rpc-go package imports — no build-time coupling or circular dependency risk Requires the rpc binary to be present on the device alongside rpc-discovery
The rpc binary can be updated independently without rebuilding the agent Subprocess invocation adds process-spawn overhead per scan cycle
DLL consumers of rpc-go are completely unaffected Parsing JSON output of rpc amtinfo --json creates a soft API contract that can break on rpc-go output changes
Service harness, scheduling, and OS collection are fully self-contained Two binaries to deploy and keep in sync on the end device
Versioned independently from rpc-go Error handling across process boundaries is more complex
Approach B — Extend rpc-go to run as an agent (rpc agent subcommand)

Add an agent subcommand to the existing rpc binary. When invoked as rpc agent --start, the process forks into the background (or registers itself as a service) and begins periodic discovery reporting.

rpc agent start  →  installs and starts the service
rpc agent stop   →  stops the service
rpc agent status →  reports current state
Pros Cons
Single binary to distribute — consistent with current rpc.exe packaging rpc.exe is already used as a DLL (via go build -buildmode=c-shared); a service harness in the same binary adds complexity to the DLL surface
Authentication, Console URL, and profile config are shared with existing flags Running as a long-lived Windows Service requires the binary to handle SCM control signals, which conflicts with the current short-lived CLI lifecycle
Reuses all existing internal packages directly — no factoring needed Agent start/stop/status commands add scope to an already large binary
One fewer artifact to manage in CI/CD Versioning the agent and the CLI together may create undesirable coupling
Interactive rpc commands and the background agent would share the same process namespace if launched from the same binary instance
Recommendation

Approach A (standalone agent) is preferred for production use. The DLL usage of rpc-go (buildmode=c-shared) makes embedding a persistent service harness in the same binary architecturally risky. Because rpc-discovery shells out to the rpc binary as a subprocess rather than importing rpc-go packages, there is no build-time coupling — both binaries are simply co-deployed on the device.

As a short-term option for prototyping, Approach B is acceptable if the DLL build target is excluded from the agent subcommand (i.e. the agent command is compiled out of the shared library variant).


3. Agent Distribution and Bootstrap

A key deployment challenge is getting the discovery agent binary — and the credentials it needs to report back to Console — onto each managed or to-be-managed device without manual steps.

Proposed Distribution Model: Console-hosted agent download API

Console exposes two endpoints that an operator (or an IT automation tool) calls in sequence: one to download the platform-appropriate agent binary and one to download a pre-populated configuration file containing Console credentials. Both steps are authenticated; the operator deploys the binary and config together onto the device.

New Console endpoints:

Method Path Description
GET /api/v1/discovery/agent?platform=windows|linux Returns the rpc-discovery binary for the requested platform as an application/octet-stream download
GET /api/v1/discovery/agent/config Returns a signed agent-config.yaml (YAML) containing the Console URL, a scoped API token, and the reporting interval — generated per request for the authenticated operator

Bootstrap flow:

sequenceDiagram
    actor Operator
    participant Console
    participant Automation as GitHub Release
    participant Device as Target Device
    participant Agent as rpc-discovery (service)

    Operator->>Console: Authenticate (browser or CLI)
    Console-->>Operator: Session established

    Operator->>Console: GET /api/v1/discovery/agent?platform=windows|linux
    Console-->>Operator: rpc-discovery binary (application/octet-stream)

    Operator->>Console: GET /api/v1/discovery/agent/config
    Console-->>Operator: agent-config.yaml (Console URL, scoped JWT, interval)

    Operator->>Device: Deploy binary and config file

    Note over Device: Windows: rpc-discovery install && rpc-discovery start<br/>Linux:   systemctl enable --now rpc-discovery

    Device->>Agent: Service starts on boot

    loop Every reportInterval seconds
        Agent->>Device: subprocess: rpc amtinfo --all --json
        Device-->>Agent: AMT JSON data
        Agent->>Device: OS collection (LMS probe, adapters, OS details)
        Device-->>Agent: OS data
        Agent->>Console: POST /api/v1/devices/discovery (Bearer JWT)
        Console-->>Agent: 200 OK
    end
Loading

Configuration file (agent-config.yaml) structure:

consoleURL: https://console.example.com
token: <scoped-JWT>          # Console issues a long-lived discovery-scoped token
reportInterval: 3600         # seconds between scan cycles
skipCertCheck: false

Security considerations:

  • The token issued in the config file should be scoped to the discovery:write permission only — it cannot manage devices or read credentials.
  • Console should support token revocation per device or per batch to allow decommissioning agents without rotating the global credential.
  • The binary served from /api/v1/discovery/agent should be the official release artifact (checksummed and optionally code-signed); Console stores it as a static asset and does not build it on demand.
  • The config download endpoint (/api/v1/discovery/agent/config) requires authentication so that only authorised operators can generate agent credentials.

Alternative delivery mechanisms (for environments without direct Console access from end devices):

  • Agent config can be baked into a GPO/MDM deployment package alongside the binary (using the standalone /api/v1/discovery/agent/config endpoint to generate credentials).
  • For air-gapped environments, the binary and a manually populated agent-config.yaml can be distributed together via standard software deployment tooling (SCCM, Ansible, etc.).

Clone this wiki locally