---
module: agent.hw-inventory
title: Hardware Inventory — ATM hardware/OS inventory sync + CDN bandwidth probe
tab: Claude
order: 40
audience: dev
---

> **Internal · agent reference.** Terse. Confirmed against `hiveops-module-hw-inventory` source 2026-07-01.

## Identity
- Module name (`AgentModule.getName()`): **`hw-inventory`**
- Maven artifact: **`hiveops-module-hw-inventory`** (own version `1.0.0`; parent `hiveops-agent-parent` currently `4.4.3`)
- Entry class: `com.hiveops.hwinventory.HardwareInventoryModule` (SPI file: `META-INF/services/com.hiveops.core.module.AgentModule`)
- Other classes: `HardwareCollector` (interface), `LinuxHardwareCollector`, `WindowsHardwareCollector`
- Log package (`getLogPackage()`): `com.hiveops.hwinventory` (→ per-module log, convention `logs/modules/hw-inventory.log` — path unverified)
- Dependencies (`getDependencies()`): **none** (empty list)
- Drop-in JAR: `modules/hiveops-module-hw-inventory.jar`; in **both** build-dist module sets (`atm` default **and** `srv`)

## Config properties
| Key | Default | Notes |
|---|---|---|
| `hw.inventory.enabled` | `true` | `false` → module inactive |
| `hw.inventory.sync.interval.hours` | `24` | fixed-rate resync period (hours) |

Reused (not owned):
- `agent.endpoint` (preferred) **or** `incident.endpoint` (fallback) — HTTP base URL; **neither set → inactive**
- `incident.institution.key` — institution key; sent in payload + as `X-Institution-Key` on the CDN probe
- `agent.update.check.url` — CDN manifest for probe; default `https://cdn.bcos.cloud/downloads/agent/latest.json`
- `task.hotfix.network.cap` — probe speed multiplier; default `0.9`, clamped `0.01`–`1.0`
- `modules.disabled` — comma list; include `hw-inventory` to disable
- auth token / host / timeouts via `HttpClientHelper.loadSettingsFromProperties(props, "agent"|"incident")`

## Events / status strings emitted
- **None.** No `EventType`, no `IAT_*`, no journal/incident events, no fleet task status. This module does **not** call the journal-event path.
- Sole output = one JSON POST of `HardwareInventoryRequest`.

## HTTP endpoint used (agent → agent-proxy → incident)
| Call | Method + Path |
|---|---|
| Inventory sync | `POST {endpoint}/api/atms/hardware/sync` (`IncidentEventClient.syncHardware`) |
| CDN probe (manifest) | `GET {agent.update.check.url}` |
| CDN probe (speed test) | `GET <patch url>` with header `Range: bytes=0-524287` |

Accepts `200/201/202` on sync; else throws `IOException`.

## Payload fields (`HardwareInventoryRequest`)
- Identity: `atmName`, `country`, `institutionKey`
- Both OS: `cpu{name,cores,maxClockMhz}`, `system{manufacturer,model,type}`, `disks[]{model,serialNumber,sizeBytes,mediaType}`, `drives[]{letter,fileSystem,totalBytes,freeBytes}`, `nics[]{name,macAddress,speedBps,rxBytesPerSec,txBytesPerSec}`, `biosSerial`, `lastBootTime`, `osVersion`, `cdnDownloadSpeedBps`
- Windows-only: `osInstallDate`, `installedPatches[]`, `installedPatchDates{}`, `installedPrograms[]{name,version,installedDate}`, `usbLockStatus`, `windowsLockStatus`

## Gotchas
- **Syncs twice at startup:** once in `initialize()`, then again from `start()`'s `scheduleAtFixedRate(..., 0, ...)` (initial delay 0).
- **~5s added per sync:** NIC RX/TX throughput is measured by sampling twice, 5s apart (`Thread.sleep(5000)`).
- **Partial payloads are normal:** every collector step is independently try/caught; failures are logged and the rest is still sent.
- **CDN probe never blocks the sync:** on any failure it returns `0` for `cdnDownloadSpeedBps`; inventory still POSTs.
- **Windows path = PowerShell/CIM**, not `wmic` (pom `<description>` is stale on this).
- **Server (`srv`) devices also run this module** — it's not ATM-only.
- Linux physical-NIC filter: skips `lo`, virtual ifaces (no `/sys/class/net/<n>/device` symlink), and MACs starting `00:00:00`.
- No client-side `atmId` resolution — platform maps `atmName`+`country` server-side.

## Do NOT
- Do **not** expect `IAT_*`/journal events from this module — it emits none.
- Do **not** look for a poll loop / fleet-task handling — it doesn't poll; it's push-only on a timer.
- Do **not** add a `hw-inventory.*`-namespaced key expecting it to work; only `hw.inventory.enabled` and `hw.inventory.sync.interval.hours` exist.
- Do **not** assume the collectors go through `PlatformService` — only `isWindows()` does; collection reads OS sources directly.
- Do **not** point this at incident directly on modern ATMs — route via `agent.endpoint` (agent-proxy).
