---
module: agent.remote
title: Remote Control Module — on-demand screen capture & file access on ATMs
tab: Internal
order: 20
audience: dev
---

> **Internal · ops/support view.** Confirmed against `hiveops-module-remote` source (`RemoteModule`, agent parent v4.4.3) on 2026-07-01.

## What it does (and doesn't)

`hiveops-module-remote` (module name `hiveops-remote`, class `com.hiveops.remote.module.RemoteModule`) is **not** a monitor and does **not** emit incident/journal events. It is an **on-demand remote-control executor**. It sits idle, registers with the **HiveIQ Remote server** (`hiveops-remote`, internal port 8090), long-polls it for a single queued command, executes it, and uploads the binary result back. There is no continuous monitoring, no baseline, and no `IAT_*` / `EventType` reporting from this module.

Supported commands (the module's advertised `capabilities`):

| Capability | What the agent does |
|---|---|
| `SCREENSHOT` | Captures the ATM's display via AWT `Robot`, encodes JPEG, uploads bytes in chunks |
| `FILE_LIST` | Lists a directory (name, size, mtime, type, POSIX perms) and returns JSON |
| `FILE_DOWNLOAD` | Streams a single file's bytes back in chunks |

`SYSTEM_INFO` exists in the `CommandType` enum but is **not** advertised as a capability and is **not** handled — if the server ever sends it the agent replies `FAILED: Unknown command type`.

Flow at a glance:
1. On `start()`, the module `POST`s to `/api/v1/agents/register` on the remote server and stores the returned Bearer token.
2. It polls `GET /api/v1/agents/poll` every **5s** (`remote.poll.interval.ms`).
3. When `hasCommand=true`, it sets the command `IN_PROGRESS`, runs it, then reports `COMPLETED` (with result/binary) or `FAILED`.
4. Binary results (screenshot JPEG, file bytes) are uploaded in **1 MiB** chunks with a per-chunk SHA-256 checksum to `POST /api/v1/agents/commands/{id}/upload`.

> This module talks to the **hiveops-remote** service (`/api/remote/`, port 8090) — **not** to agent-proxy or hiveops-incident. It is a separate path from the standard journal/event pipeline.

## Config properties it registers

Registered in `getDefaultConfiguration()` (namespace `remote.*`):

| Key | Default | Meaning |
|---|---|---|
| `remote.enabled` | `false` | Master on/off. Module only loads when `true`. |
| `remote.server.url` | `http://localhost:8090` | Remote-server base URL. **Required** — init throws if blank. |
| `remote.poll.interval.ms` | `5000` | Delay between polls for a queued command. |
| `remote.poll.timeout.ms` | `15000` | Registered default, but **not referenced anywhere in code** (no effect — unverified whether wired in a newer build). |
| `remote.chunk.size.bytes` | `1048576` | Upload chunk size (1 MiB) for binary results. |
| `remote.max.file.size.bytes` | `104857600` | Max file size (100 MiB) for `FILE_DOWNLOAD`; larger files fail. |

Also read at init but **not** in the defaults set (must be added explicitly to take effect):

| Key | Default if unset | Meaning |
|---|---|---|
| `remote.machine.id` | derived from hostname (then random UUID) | Stable machine identity sent at registration. |
| `remote.allowed.paths` | *(empty = all paths allowed)* | Comma/semicolon list; if set, file ops are restricted to these base dirs. |
| `remote.blocked.paths` | OS default block list | Comma/semicolon list of blocked path patterns (`*` wildcard). Defaults block `/etc/shadow`, `/etc/passwd`, `/etc/sudoers`, `/root/.ssh`, `/home/*/.ssh` (Linux) and `C:\Windows\System32\config`, `...\drivers`, credential stores (Windows). |

## Enabled / deployed to ATMs

- **Enable condition:** module self-enables only when `remote.enabled=true`. It is **off by default**. Turn it on for a device/institution with a `CONFIG_UPDATE` fleet task setting `remote.enabled=true` (and, on locked-down ATMs, `remote.allowed.paths`).
- **Shipped how:** it's a drop-in JAR (`modules/hiveops-module-remote.jar`), not baked into the fat JAR. Jackson is shaded into the JAR (hiveops-core is `provided`).
- **Retrofitting an ATM that doesn't have the JAR:** deliver via a fleet `INSTALL_MODULE` task — it drops the JAR into `modules/` and restarts the agent (`ProcessInstallModule`). The filename must end in `.jar`.

## Common failure modes / what to check

| Symptom | Likely cause | What to check |
|---|---|---|
| Module never registers / no commands run | `remote.enabled` not `true`, or module JAR absent | Confirm `remote.enabled=true` and that `modules/hiveops-module-remote.jar` is present; check agent log for `Initializing remote module for server: ...` |
| Init fails: `remote.server.url is required` | Property blank/missing | Set `remote.server.url` to the remote server (prod path is via `/api/remote/`, internal `:8090`). |
| Registration logs `Failed to register with remote server` | Server unreachable / auth rejected | Registration errors are swallowed (no retry loop) — the module keeps polling with a null token, so polls 401. Verify network path to the remote server and that it accepts the registration payload. |
| `SCREENSHOT` fails: `Screen capture not available in headless mode` | ATM/JVM is headless (no X display / service session) | Screen capture needs a real graphics environment + AWT `Robot`. Headless Linux boxes and some Windows service sessions can't capture. |
| `FILE_DOWNLOAD` fails: `File exceeds maximum size limit` | File > `remote.max.file.size.bytes` (100 MiB default) | Raise the limit via config, or collect large files with a fleet `FILECOLLECT`/`LOG_COLLECT` task instead. |
| File op fails: `Access to path is blocked` / `Path is not within allowed directories` | Hit a `remote.blocked.paths` pattern, or path is outside `remote.allowed.paths` | Expected security behavior — adjust the allow/block lists if the target should be reachable. |
| File op fails: `Path contains directory traversal sequences` / `null bytes` | Path contained `..` or `\0` | Path-traversal guard (`PathSecurityUtils`) — send a normalized absolute path. |
| Upload fails mid-transfer | Network drop, or server rejected a chunk (checksum/size) | Uploads are **not** retried by this module — the command goes `FAILED`. Re-issue the command. |
