RMMMax Linux Agent Service
**Version:** 1.0.0
**File:** `rmmmax_agent.py`
**Installed to:** `/var/rmmmax/agentservice/`
**Command:** `rmmmax-agent`
—
Overview
The RMMMax Linux Agent Service is a lightweight Python 3 daemon that connects a Linux machine to the RMMMax platform. It runs as a background systemd service, checks in with the RMMMax API every five minutes, receives commands dispatched from the platform, executes them, and reports results back. It requires no external Python packages — only the Python 3 standard library.
It is the Linux equivalent of the Windows RMMMax Agent Service and communicates with the same backend API endpoints.
—
How It Works
1. Registration
Before the service can operate, the machine must be registered with RMMMax using:
bash rmmmax-agent register
During registration the agent:
1. Collects local system information — hostname, IP address, OS name and version (read from `/etc/os-release`), current user, and UTC offset.
2. Posts this data along with the operator’s RMMMax username and password to `POST /api/v1/agentservice/register/`.
3. The server authenticates the credentials, enforces the plan’s agent limit, auto-creates the Client record if it does not exist, and returns a unique `computerID` (UUID) and a `token` (64-character hex string).
4. Both values are saved immediately to `/var/rmmmax/agentservice/config.json` with permissions set to `root:root 600` so no other user or process can read the token.
5. The server automatically queues a `management_package` command so the management scripts (`lum.sh`, `upgrades.sh`) are deployed to the agent on its first check-in.
The agent is considered registered when `config.json` contains a non-empty `computerID` and `token`.
—
2. The Check-In Loop
Once the service is started (`systemctl start rmmmax-agent`), it enters a continuous check-in cycle:
“`
Service starts
│
├── Check-in immediately on startup
│ POST /api/v1/agentservice/checkin/
│ ↓
│ Server returns: newToken + command (or “sleep”)
│ ↓
│ New token saved to config.json immediately
│ ↓
│ If command received → execute it → loop back with result
│ If “sleep” → wait 5 minutes → check-in again
│
└── Repeat every 5 minutes indefinitely
“`
Every check-in payload includes the current token, hostname, IP, OS, username, UTC offset, client name, location name, and agent version. This keeps the server’s agent record up to date on every cycle.
**Token rotation** occurs on every single check-in. The server issues a new token with each response and stores the old one as a `previous_token` for one-shot recovery. The agent saves the new token to disk before doing anything else, so a crash between receiving the response and the next check-in does not permanently break the authentication chain.
—
3. Command Execution
When the server has work queued, it returns a command object with three fields: `id`, `commandType`, and `payload.script`. The agent executes the script and reports the result (`exitCode`, `stdout`, `stderr`) on the immediately following check-in.
There are two execution modes:
#### Mode A — WORK= commands (management scripts)
When `payload.script` starts with `WORK=`, the agent routes the command to one of two dedicated shell handlers installed at `/var/rmmmax/agentservice/`:
| `commandType` contains | Handler called |
|—|—|
| `lum` | `lum.sh` — Linux Update Manager (apt / yum / zypper) |
| `update` or `upgrade` | `upgrades.sh` — Cross-platform Update Manager |
The handler is called with the full `WORK=` string as its argument, which it decodes to retrieve the auth token, computer ID, API base URL, command name, and any command-specific data. The handler posts its own results directly to the respective tool endpoint (e.g. `/api/v1/lum/post_lum_data_from_agent/`).
#### Mode B — Raw bash scripts
For all other command types, the agent:
1. Writes the script to a uniquely-named temporary file at `/tmp/rmmmax_cmd_<uuid>.sh`.
2. Prepends `#!/bin/bash` and `set -euo pipefail`.
3. Sets permissions to `700` (root-only execute).
4. Runs it with `/bin/bash` using `subprocess.run()`, capturing both stdout and stderr.
5. Deletes the temp file whether the script succeeded or failed.
6. Returns `{exitCode, stdout, stderr}` to the server on the next check-in.
Scripts have a **10-minute execution timeout**. If a script runs longer, it is terminated and the result `{exitCode: -1, error: “Script execution timed out”}` is reported.
A threading lock (`_busy_lock`) prevents two command executions from overlapping. If a command is still running when the 5-minute timer fires again, that check-in cycle is skipped and logged as a warning.
—
4. Failure Handling
The agent is designed to keep running through failures rather than crash.
| Scenario | Behaviour |
|—|—|
| Network unreachable / timeout | HTTP call returns `None`; current cycle is abandoned; retries at next 5-minute tick |
| HTTP 4xx / 5xx errors | Logged as a warning; cycle abandoned; retries at next tick |
| Response JSON parse failure | Logged as a warning; cycle abandoned; retries at next tick |
| Token missing from response | Logged as warning; cycle abandoned; retries at next tick |
| Script execution timeout | Script process killed after 10 minutes; error result reported to server |
| Config write failure | Logged as an error; not fatal — service continues with in-memory config |
| Stale token (missed response) | Server recognises `previous_token`; performs a fresh rotation automatically; no manual intervention needed |
| Service crash (any reason) | systemd restarts the service after 30 seconds (`Restart=always`) |
| Machine rebooted | systemd auto-starts the service on every boot (`WantedBy=multi-user.target`, enabled on install) |
—
5. Configuration File
All persistent identity data is stored at:
“`
/var/rmmmax/agentservice/config.json
“`
| Field | Description |
|—|—|
| `computerID` | UUID assigned by the server at registration |
| `token` | Current rotating authentication token (64-char hex) |
| `clientID` | UUID of the associated client on the server |
| `clientName` | Client organisation name sent on every check-in |
| `locationName` | Location/site name sent on every check-in |
| `apiBaseUrl` | API endpoint base (default: `https://api.rmmmax.com/api/v1/agentservice`) |
| `agentVersion` | Agent version string reported to the server |
The file is written **atomically** — the agent writes to `config.json.tmp` then calls `os.replace()` to swap it in. This means a crash or power failure mid-write can never produce a corrupt config file.
File permissions: `600` (readable and writable by root only).
—
6. Logging
Log file: `/var/rmmmax/agentservice/activity.log`
Every log entry is timestamped and tagged with a severity level:
“`
2026-04-17 09:15:02 [INFO] RMMMax Agent Service v1.0.0 starting (PID 1234) …
2026-04-17 09:15:03 [INFO] Check-in OK — no pending commands.
2026-04-17 09:20:03 [INFO] Received command #42 type=’lum_scan’
2026-04-17 09:20:07 [INFO] Command #42 finished: exitCode=0
“`
Log management rules:
– **Rotation:** When `activity.log` reaches 500 KB it is renamed to `activity.log.1` and a fresh log is started. Only one backup is kept.
– **Daily truncation:** At midnight the log is cleared and the backup is removed, preventing unbounded growth.
– All log entries are also written to **stdout**, which systemd captures in the system journal (`journalctl -u rmmmax-agent`).
—
7. CLI Management Commands
All commands require root or `sudo`. The service does not need to be stopped to run management commands; config changes take effect on the next check-in.
| Command | What it does |
|—|—|
| `rmmmax-agent register` | Register this machine for the first time |
| `rmmmax-agent reregister` | Re-authenticate using credentials without losing the existing `computerID` or historical data. Used after a token is lost (e.g. config file deleted). |
| `rmmmax-agent update –client-name “X” –location-name “Y”` | Change the client name and/or location stored locally. The change reaches the server on the next check-in. |
| `rmmmax-agent status` | Print registration state and the 20 most recent log lines. |
| `rmmmax-agent deregister` | Remove the agent from the RMMMax server and wipe the local config. Requires interactive confirmation. |
| `rmmmax-agent run` | Start the service loop (called internally by systemd — not for interactive use). |
—
8. systemd Integration
The service unit file (`/etc/systemd/system/rmmmax-agent.service`) configures the following behaviour:
| Setting | Value | Effect |
|—|—|—|
| `After=network-online.target` | — | Waits for full network connectivity before starting, preventing failed check-ins immediately after boot |
| `Restart=always` | — | Restarts after any exit — crash, signal, or non-zero exit code |
| `RestartSec=30` | 30 seconds | Delay before restart, giving the network time to recover |
| `KillMode=mixed` | — | Sends SIGTERM to the process group, cleaning up any running child scripts |
| `TimeoutStopSec=15` | 15 seconds | Allows in-progress scripts up to 15 seconds to finish before a forced kill |
| `WantedBy=multi-user.target` | — | Auto-starts on every normal system boot |
The agent handles `SIGTERM` and `SIGINT` gracefully — it sets an exit flag and waits for the current sleep interval to expire before shutting down cleanly.
—
9. File Layout
“`
/var/rmmmax/agentservice/
├── rmmmax_agent.py ← the agent itself
├── config.json ← registration state (root:root 600)
├── activity.log ← current log
├── activity.log.1 ← previous log (rotation backup)
├── lum.sh ← Linux Update Manager handler (deployed by server)
└── upgrades.sh ← Cross-platform Update Manager handler (deployed by server)
/usr/local/bin/
└── rmmmax-agent ← CLI wrapper (exec python3 …/rmmmax_agent.py “$@”)
/etc/systemd/system/
└── rmmmax-agent.service ← systemd unit file
“`
—
10. Security Notes
– The config file (`config.json`) contains the authentication token and is restricted to `root:root 600`.
– Temporary script files are created in `/tmp` with permissions `700`, executed, then immediately deleted.
– The service runs as `root` only because management scripts (package installs, system updates) require root privileges.
– The authentication token rotates on every single check-in — a captured token is only valid until the agent next contacts the server (at most 5 minutes).
– Credentials (username and password) are used only during `register` and `reregister` — they are never stored on disk.