# HarborForge.Monitor Lightweight Go telemetry client that reports server hardware status to the HarborForge backend. Part of the [HarborForge](../README.md) platform. - Role: standalone telemetry agent; **does not depend on OpenClaw**, suitable for plain Linux hosts, VPS, Nginx boxes, etc. - Reports to the HarborForge backend (`POST /monitor/server/heartbeat`). - Optional local bridge HTTP server on `127.0.0.1:` (default port `9100`) for the HarborForge OpenClaw plugin. ## Collected Metrics - CPU usage (`cpu_pct`) - Memory usage (`mem_pct`) - Disk usage (`disk_pct`, for the root / `rootFs` filesystem) - Swap usage (`swap_pct`) - Load average (`load_avg` — 1/5/15 min) - Uptime (`uptime_seconds`) - Nginx installed (`nginx_installed`) - `/etc/nginx/sites-enabled` listing (`nginx_sites`) When OpenClaw metadata has been pushed to the bridge, heartbeats are additionally enriched with `openclaw_version`, `plugin_version`, and `agents`. ## Reporting Endpoint The client sends: - `POST /monitor/server/heartbeat` - Header: `X-API-Key: ` - JSON body: the telemetry payload described above ## Project Structure ```text HarborForge.Monitor/ ├── cmd/harborforge-monitor/ # Program entry point (main.go) ├── internal/config/ # Config loading (file + env + flags) ├── internal/telemetry/ # Metric collection and reporting ├── internal/bridge/ # Local MONITOR_PORT bridge server ├── systemd/ # systemd unit file ├── Dockerfile # Container build ├── docker-compose.yml # Docker Compose configuration ├── config.example.json └── README.md ``` ## Configuration First register the server in the HarborForge backend and generate an API Key. Then prepare a config file, e.g. `/etc/harborforge-monitor/config.json`: ```json { "backendUrl": "https://monitor.hangman-lab.top", "identifier": "vps-nginx-01", "apiKey": "your-api-key", "reportIntervalSec": 30, "logLevel": "info", "rootFs": "/host", "monitorPort": 9100 } ``` Resolution order is: defaults → config file → environment variables → command-line flags (later wins). `apiKey` is required; the process exits if it is empty. ### Environment Variables Both the (intentionally compatible) `HF_MONITER_*` spelling and the `HF_MONITOR_*` spelling are accepted; `HF_MONITER_*` is checked first. | Variable | Purpose | Default | |----------|---------|---------| | `HF_MONITER_BACKEND_URL` / `HF_MONITOR_BACKEND_URL` | Backend base URL | `https://monitor.hangman-lab.top` | | `HF_MONITER_IDENTIFIER` / `HF_MONITOR_IDENTIFIER` | Server identifier | hostname | | `HF_MONITER_API_KEY` / `HF_MONITOR_API_KEY` | Server API key | (required) | | `HF_MONITER_REPORT_INTERVAL` / `HF_MONITOR_REPORT_INTERVAL` | Report interval (seconds) | `30` | | `HF_MONITER_LOG_LEVEL` / `HF_MONITOR_LOG_LEVEL` | Log level | `info` | | `HF_MONITER_ROOTFS` / `HF_MONITOR_ROOTFS` | Host root filesystem mount (for container use) | (empty) | | `MONITOR_PORT` / `HF_MONITOR_PORT` | Local bridge port (`0` = disabled) | `0` | When `rootFs` is set, `HOST_PROC` / `HOST_SYS` / `HOST_ETC` / `HOST_VAR` / `HOST_RUN` are derived from it (if not already set) so that gopsutil reads host metrics instead of the container's. ### Command-line Flags ```text -config string Path to config file (default "/etc/harborforge-monitor/config.json") -once Collect and send telemetry once, then exit -print-payload Print the payload JSON before sending -dry-run Collect telemetry but do not send it -version Print version and exit -backend-url string Override backend URL -identifier string Override identifier -api-key string Override API key -report-interval int Override report interval in seconds -log-level string Override log level -rootfs string Override root filesystem path -monitor-port int Override monitor bridge port ``` ### MONITOR_PORT — Plugin Bridge When `MONITOR_PORT` (or `monitorPort`) is greater than 0, Monitor starts a local HTTP server on `127.0.0.1:` for the HarborForge OpenClaw plugin to query telemetry. | Endpoint | Method | Description | |----------|--------|-------------| | `/health` | `GET` | Health check; returns status, `monitor_version`, and `identifier` | | `/telemetry` | `GET` | Returns the latest cached telemetry snapshot | | `/openclaw` | `POST` | Receives OpenClaw metadata (version, plugin version, agents) from the plugin | After the plugin pushes metadata via `POST /openclaw`, Monitor attaches `openclaw_version`, `plugin_version`, and `agents` to subsequent heartbeats. If the plugin never pushes metadata, these fields are omitted and heartbeats are unaffected. **Important:** the bridge is optional. If `MONITOR_PORT` is 0 or unset, the bridge does not start. Even if the bridge fails to start, heartbeat reporting continues normally (bridge errors are logged as non-fatal). ## Local Development ```bash go mod tidy go build ./cmd/harborforge-monitor ./harborforge-monitor -config ./config.example.json -dry-run -once ``` ## Docker Build the image: ```bash docker build -t harborforge-monitor . ``` ### Docker Compose ```bash export HF_IDENTIFIER=my-server export HF_API_KEY=your-api-key export MONITOR_PORT=9100 docker compose up -d ``` The compose file runs with `network_mode: host` and mounts the host root filesystem read-only at `/host` (the image defaults `HF_MONITER_ROOTFS=/host`). ### Manual Docker Run Run with the **host rootfs mounted read-only** so the container collects host metrics instead of its own: ```bash docker run -d \ --name harborforge-monitor \ --restart unless-stopped \ --network host \ -v /:/host:ro \ -e HF_MONITER_BACKEND_URL=https://monitor.hangman-lab.top \ -e HF_MONITER_IDENTIFIER=my-server \ -e HF_MONITER_API_KEY=your-api-key \ -e HF_MONITER_ROOTFS=/host \ -e MONITOR_PORT=9100 \ harborforge-monitor ``` ## systemd You can also run the compiled binary via systemd: ```bash go build -o /usr/local/bin/harborforge-monitor ./cmd/harborforge-monitor cp systemd/harborforge-monitor.service /etc/systemd/system/ systemctl daemon-reload systemctl enable --now harborforge-monitor ``` The provided unit runs `harborforge-monitor -config /etc/harborforge-monitor/config.json` as `root` with `Restart=always`.