# Changelog All notable changes to the Unraid Management Agent will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). --- ## [Unreleased] ## [2026.06.10] - 2026-06-26 ### Added - **Native HTTPS/TLS for the HTTP & MCP server** (#131) — the agent can now serve the REST API and the `/mcp` endpoint over HTTPS when pointed at a PEM certificate/key pair via `--tls-cert-file`/`--tls-key-file`, the `TLS_CERT_FILE`/`TLS_KEY_FILE` environment variables, or `tls_cert_file`/ `tls_key_file` in `config.yml`. TLS is enabled only when both files are set; an invalid, half-configured, or unloadable pair logs a warning and falls back to plain HTTP so a stale path can never make the agent unreachable. mDNS discovery now advertises a `scheme=https`/`scheme=http` TXT record and the `/network/access-urls` endpoint returns `https://…` URLs when TLS is active. ### Fixed - **Claude Desktop "HTTP is not supported" when adding the agent** (#131) — the Claude integration guide now documents that Custom Connectors are reached from Anthropic's cloud (so a LAN URL is unreachable even over HTTPS) and gives the two paths that actually work: the `mcp-remote --allow-http` stdio bridge for LAN-only use (no TLS required), and native HTTPS with a publicly-trusted certificate behind a port-forward/tunnel for Custom Connectors. ## [2026.06.09] - 2026-06-16 ### Fixed - **The agent no longer fights third-party fan-control plugins** (#128) — fan state is restored on shutdown only for fans the agent actually modified. Previously `RestoreAll()` ran on every daemon shutdown (including plugin auto-updates) and wrote `pwm_enable` to **every** controllable fan even when the user never enabled the agent's fan control, yanking the enable-mode out from under controllers like FanCTRL Plus and Dynamix Auto Fan Control. The agent now tracks which fans it wrote to and leaves all others untouched, so a monitor-only install performs zero fan writes. ### Added - **Automatic stand-down when a third-party fan controller is active** (#128) — the agent now detects **FanCTRL Plus** and **Dynamix Auto Fan Control** (each counted active only when installed _and_ enabled, via its `service="1"` config or a running control process). While one is active the agent stays monitor-only: `SetSpeed`, `SetMode`, `SetProfile`, `RestoreDefaults` refuse with a clear "deferred to active plugin" error and the emergency full-speed override is suppressed (the active plugin owns thermal response). Detection is evaluated live, so enabling a plugin after startup takes effect without a restart. The fan status (`GET /api/v1/fan`, MQTT, dashboard feed) now includes an `external_control` object reporting whether the agent is deferring and to which controller. ## [2026.06.08] - 2026-06-15 ### Added - **Unassigned Devices Root Shares are now reported** (ha-unraid-management-agent#83) — the agent previously skipped UD "Root Share" entries. It now surfaces them in `GET /api/v1/unassigned` with `type: "root"`, covering both configured `protocol="ROOT"` entries in `samba_mount.cfg` (e.g. `//server/mnt/user`) and live local root shares gathered under `/mnt/rootshare/` (a `fuse.shfs` mount). - **Per-share status debug logging for Unassigned remote shares** — at debug level the collector now logs each remote share's `source`, `type`, `status`, and `mount_point` every cycle, so a "HA shows mounted but Unraid shows unmounted" report can be checked against exactly what the agent served (ha-unraid-management-agent#83). - **Diagnostics tools in the plugin UI** — the **Diagnostics** section now has a **Download Diagnostics** button and a **Run Self-Test** button: - **Download Diagnostics** saves a redacted ZIP (system state, array, containers, VMs, network, recent agent & system logs, and configuration with secrets removed) to attach to bug reports. It is served by a new `GET /api/v1/diagnostics/bundle` endpoint — the same bundle the CLI `diagnostics` command produces, now downloadable from the browser. The download streams through a same-origin PHP proxy so it works in every browser (Safari blocks cross-origin downloads) and on HTTPS webGUIs. - **Run Self-Test** calls `GET /api/v1/diagnostics/self-test` (via the same proxy, since the agent does not send CORS headers) and shows the Unraid version, overall data-source health, capability count, and per-subsystem status inline. - **Debug logging toggle in the plugin UI** — a new **Diagnostics** section on the settings page with a **Debug Logging** switch, so verbose logging can be enabled from the web UI instead of editing `config.cfg` over SSH. Off keeps the level at `info`; on sets `debug`. A manually-set `warning`/`error` level is preserved unless the toggle is touched. Applying restarts the service. - **Collector stall diagnostics (watchdog) across the I/O-bound collectors** (investigating ha-unraid-management-agent#83, #123) — each collect cycle now logs its start and duration at debug level, and a watchdog logs a warning plus a full goroutine stack dump if a cycle runs longer than an **interval-derived threshold** (the collector's interval, clamped to 30s–5min), then logs again when it eventually finishes. Collectors run cycles serially on one goroutine, so a single blocked cycle stops that collector's updates until it returns; this makes the blocking call visible in the agent log. Wired into the collectors that shell out or do blocking I/O — unassigned, disk, docker, zfs, ups, nut, network, shares, gpu, hardware, mover, and array — covering both startup and periodic cycles. The watchdog is purely observational; it never cancels or alters a cycle. Set the plugin log level to **debug** to capture this. - **WebSocket slow-client eviction is now logged** — when a client's send buffer fills (it cannot keep up with the broadcast rate, e.g. on a congested link) the hub evicts it so other clients are unaffected; this previously happened silently and surfaced to the consumer only as a dropped connection ("EOF"). The eviction now logs a warning naming the client and topic, so the disconnect is diagnosable from the agent log. ### Changed - **Left-aligned the plugin settings layout** — the form labels and inputs were centered (a wide right-aligned label column); they are now left-aligned in a compact column so the settings read naturally from the left. - **Redesigned plugin icons** — the Community Applications icon (`icon.png`) was a pixelated 256×256 upscale; it is now a crisp 512×512 PNG (CA requires 256×256 minimum with transparency) rendered from a 4× supersampled flat design (dark slate squircle, server slats with status LEDs, Unraid-orange telemetry arcs). The webGUI page icons (48/64/128 px under `meta/plugin/images/`) are exported from the same master, and `scripts/generate_icon.py` regenerates all sizes from one source. ### Fixed - **Bounded the Unassigned Devices statfs probe goroutines to prevent an unbounded leak** — capacity probes run in a goroutine because `statfs` on a dead network mount cannot be cancelled. Previously each cycle could leak one such goroutine per wedged share, so a permanently-unreachable remote server would grow the abandoned-goroutine (and pinned-OS-thread) count without bound until the daemon ran out of memory. Probes are now capped (16 in flight); once that many are stalled, new probes are skipped (the share is reported without capacity) instead of spawning more, so memory stays bounded. Also fixed a data race in the statfs timeout test so the full suite is race-clean. - **WebGUI footer stuck on "unraid-management-agent started on port …" after array start** (#123 follow-up, reported in ha-unraid-management-agent#83) — the plugin's `started`/`stopping_svcs` event scripts wrote their status line to stdout, which emhttpd captures into its `fsProgress` variable; the webGUI footer displays `fsProgress` until the next array operation overwrites it, so on systems that stay idle after boot the text lingered for many hours and made the agent look hung even when it was healthy (it remained even with the service stopped). Event scripts now route their output to syslog via `logger -t unraid-management-agent` instead of stdout, so the footer is never touched and the start/stop trace is still logged. ## [2026.06.06] - 2026-06-12 ### Added - **MCP read-only mode** (#126) — a new `READ_ONLY` setting (plugin settings page under **AI Agent Access (MCP)**, `READ_ONLY="true"` in `config.cfg`, `read_only: true` in `config.yml`, or `--read-only` / `READ_ONLY=true`) that blocks **every state-changing MCP tool** (49 of the 126 registered tools) at the server, so AI agents can only consume data. Write tools stay visible in tool listings but every invocation is rejected with a clear message, and each blocked attempt is logged with the tool name. The dual-mode diagnostic tools `system_health_report` and `run_runbook` still return their report / dry-run plan but never execute remediation actions, even with `confirm: true`. Destructive tools keep their existing `confirm: true` gating when read-only mode is off; the REST API and WebSocket are not affected. - **Configurable HTTP bind address** (#125) — a new `BIND_ADDRESS` setting (plugin settings page, `BIND_ADDRESS="…"` in `config.cfg`, `bind_address: …` in `config.yml`, or `--bind-address` / `BIND_ADDRESS=…`) that binds the API/WebSocket/MCP server to a specific IP on multi-homed / multi-VLAN systems instead of all interfaces. The settings page offers a dropdown of the IPs detected on the server's interfaces and VLANs (container/VM bridges and link-local excluded), so no manual entry is needed. mDNS discovery advertises the configured address, so Home Assistant auto-discovers the endpoint on the intended network. The address must be assigned to a local interface, and loopback addresses (127.x.x.x, ::1) are rejected so integrations can always reach the agent — invalid or stale values (e.g. after a VLAN change) log a warning and fall back to all interfaces rather than leaving the agent unreachable. Applying the setting from the plugin page validates it server-side, persists it, and restarts the service automatically, then health-probes the new endpoint to confirm the agent came back up. IPv6 addresses are supported. Defaults to all interfaces (unchanged behaviour). --- ## [2026.06.05] - 2026-06-11 ### Fixed - **Parity schedule endpoint missing `month` and `cron` from dynamix.cfg** (#124) — `GET /api/v1/array/parity-check/schedule` did not parse two keys from the `[parity]` section, so consumers (e.g. the Home Assistant integration) could not compute the next check for **yearly** schedules (missing `month`, fell back to January by coincidence) or **custom** schedules (missing raw `cron` expression — next check showed "Unknown"). Both are now parsed and exposed (`month`, `cron`). Additionally, the authoritative scheduled-check entry from `parity-check.cron` (the line Unraid actually runs, e.g. `0 0 1 1 *`) is exposed as **`check_cron`**, valid for every scheduled mode including custom, so consumers can compute the exact next check without re-implementing Unraid's scheduling rules. The DTO now also documents Unraid's conventions: `day` is cron day-of-week (0 = Sunday) and the dynamix `hour` key holds a `"minute hour"` pair (only the hour part is exposed — use `check_cron` for the exact time). - **Agent stalled for 1+ hour after array start on networks without outbound internet access** (#123) — several collector and API paths performed blocking network operations with long (or no) timeouts, so on isolated VLANs (no internet, unreachable remote-share servers) the agent kept serving stale data long after boot: - **Unassigned Devices remote shares**: the collector ran `statfs()` sequentially on every mounted SMB/NFS share with no timeout. A share whose server is unreachable blocks `statfs` in uninterruptible sleep for minutes; with many remote shares mounted at boot this wedged the whole collector for hours. Capacity probes are now bounded at **5 s per share** — on timeout the share is still reported (without capacity data) and the collector moves on. - **Docker container update checks**: `CheckAllContainerUpdates` ran under an independent 5-minute background context, with each per-container registry check holding its own 60-second context, and could not be cancelled on shutdown. Checks are now context-aware end to end (cancelled with the collector lifecycle) and bounded at **30 s overall / 10 s per container**. - **Plugin update checks**: `plugin check` (which downloads update metadata from GitHub) ran with a 120-second timeout and could not be cancelled. It is now context-aware and bounded at **30 s**; locally cached update files are still read afterwards, so previously fetched results are preserved. - **WAN IP discovery** (`/api/v1/network/access-urls`): each of the three public-IP services was tried with a 5-second timeout (up to 15 s per request on a blackholed network). Reduced to **2 s per service** (~6 s worst case); the first successful response still wins. ### Added - **`last_healthy` timestamp per subsystem** in the platform health registry, exposed via `GET /api/v1/diagnostics/self-test` (#123). The registry stamps the timestamp on every healthy report and preserves it across degradations, so a persistent `degraded`/`unavailable` state can be dated from the API ("when did this subsystem last work?"). Omitted from JSON when a subsystem has never been healthy. Documented in Swagger as RFC3339 `date-time`. ## [2026.06.04] - 2026-06-08 ### Fixed - **API rate limit throttled the Home Assistant integration** — the REST API used a single **global** bucket (**10 req/s, burst 20**), but the HA integration fetches 20-30 endpoints in parallel on every coordinator refresh (and retries rate-limited ones, amplifying the startup burst). One client's burst could exhaust the shared bucket and return **HTTP 429** on endpoints such as `/unassigned`, so the data wasn't available when the integration created entities and the **remote-share binary sensors never appeared** (ha-unraid-management-agent#83). Rate limiting is now **per-client (keyed by source IP)** at **50 req/s, burst 100 per client**, so legitimate LAN clients (the integration, the Web UI, MCP/AI clients) are never throttled by one another — only a single client genuinely running away is capped. Idle client buckets are swept after 10 minutes to bound memory. Rejections are now logged at debug for diagnosability, since rate-limited requests short-circuit before the logging middleware. Limits are named constants (`rateLimitPerSecond`, `rateLimitBurst`) guarded by a regression test. - **Disabled Docker/VM falsely reported as a degraded data source** — when Docker (`DOCKER_ENABLED="no"`) or the VM manager (`SERVICE="disable"`) was intentionally turned off in Unraid settings, the respective collector reported the subsystem as `unavailable`, which pushed `DegradedSubsystemCount > 0` and fired the **"Agent data source degraded"** alert on every server with those services off. A new `SourceDisabled` state (severity 0, **not** counted as degraded and not worsening the overall self-test state) is now reported when a service is positively detected as disabled in `docker.cfg`/`domain.cfg`. A service that is enabled but genuinely unreachable is still reported as `unavailable`, and an unreadable config conservatively keeps the old `unavailable` behavior so real faults are never masked. The subsystem's inline `source_status` now reads `disabled` instead of `unavailable`, and the Swagger `dto.SourceState` enum gained the `disabled` value. ## [2026.06.03] - 2026-06-07 ### Added - **Control feature parity with the official Unraid API** — added the safe control operations the official GraphQL API had that this agent lacked: **VM reset** (`vm_action reset` + `POST /vm/{name}/reset`, hard power-cycle via libvirt `DomainReset`, `confirm=true` required on both REST and MCP); Docker **container remove** (`container_action remove` + `POST /docker/{id}/remove`, optional image removal, `confirm=true`), **autostart control** (`POST /docker/{id}/autostart` + `set_container_autostart`, writing Unraid's `/var/lib/docker/unraid-autostart`), and read-only **port-conflict detection** (`GET /docker/port-conflicts` + `get_port_conflicts`); and array **clear-disk-statistics** (`POST /array/clear-disk-stats` + `clear_disk_stats`, via the emhttpd socket — the same path the WebUI uses). The MCP tool count is now 125. Destructive ops are confirm-gated and capability-gated. Verified live on Unraid 7.3.1. Per-disk array mount/unmount was intentionally **dropped** — Unraid exposes no safe per-disk mechanism (only array-wide start/stop) — and the dangerous add/remove-disk-to-array operations were deliberately left to the official API / WebUI. - **OS-resilience & self-diagnostics** — the agent now detects when an Unraid data source breaks (moved path, changed file format, missing binary) via capability/shape probing, degrades gracefully (keeps serving best-effort data, never silently wrong/empty), and surfaces it everywhere: `GET /api/v1/diagnostics/self-test`, the `run_self_test` MCP tool (read-only; brings the MCP tool count to 122), an inline `source_status` flag on affected subsystem responses (omitted when healthy, so healthy responses are unchanged), a `source_status_changed` WebSocket event, `unraid_subsystem_status` / `unraid_degraded_subsystem_count` Prometheus gauges, a degraded summary in `/api/v1/health/report`, and a built-in **enabled** `subsystem_degraded` alert rule. Control operations are capability-gated (clear "unavailable" errors instead of cryptic shell failures). Implemented as a new dependency-light `daemon/platform` package; fully self-contained (no dependency on the official Unraid API). Verified live on Unraid 7.3.1 (self-test healthy across 6 subsystems; golden-fixture + breakage-simulation tests in CI). - **Go runtime & process metrics on `/metrics`** — the Prometheus endpoint now exposes the standard Go runtime and process collectors (`go_goroutines`, `go_memstats_heap_inuse_bytes` and the full `go_memstats_*` family, `process_resident_memory_bytes`, `process_open_fds`, …), enabling long-term goroutine/heap/RSS leak and resource-health monitoring of the agent itself. Verified live on Unraid 7.3.1. - **Drive-aware fan curves + sensor discovery** — a fan curve can now read its temperature from a selected set of drives (the **max** of the active ones, sourced non-destructively from `disks.ini` — spun-down drives are skipped, never woken) and **fail over** to a per-profile hwmon sensor when those drives are spun down (logged once per transition, no spam). New read-only `GET /api/v1/fans/sensors` endpoint and `get_fan_sensors` MCP tool list every hwmon temperature sensor (path, label, value, plausibility flag) and drive (id, device, temp, spin state) available as a curve source — so users no longer need to hand-discover sysfs paths. `POST /fans/profile` and `set_fan_profile` accept a structured `source` (`hwmon` single sensor, or `drives` with optional fallback) while remaining backward compatible with the legacy `temp_sensor_path`; existing `fancontrol.json` assignments migrate transparently on load. Source paths are validated to the `/sys/class/hwmon/hwmonN/tempM_input` shape and drive IDs are sanity-checked. The emergency thermal cutoff remains **hwmon-only** by design (a spun-down array can never suppress it). MCP tool count → 126. Mirrors the `SimonFair/IPMI-unRAID` drive-selection model. Verified live on Unraid 7.3.1. ### Fixed - **Fan-safety log spam** — the fan safety guard logged a "fan appears stalled" warning on **every** poll cycle for each driven-but-not-spinning header (e.g. empty/unused fan headers reading 0 RPM), producing thousands of warnings. It now logs the warning **once per transition** into the stalled state (and a recovery line once when it clears), mirroring the OS-resilience registry's transition logging. Verified on Unraid: warnings/36-min dropped from ~3,600 to single digits. - **Clean-shutdown logged at ERROR** — a graceful HTTP-server shutdown (`http.ErrServerClosed`) was logged as `ERROR: API server error`; it is now an informational "API server stopped" line. ## [2026.06.02] - 2026-06-06 ### Added - **Zeroconf/mDNS auto-discovery** (#120) — the agent now advertises itself on the local network via mDNS/DNS-SD as `_unraid-mgmt-agent._tcp.local.`, so integrations such as the [Home Assistant integration](https://github.com/ruaan-deysel/ha-unraid-management-agent) can auto-discover the server instead of requiring a manually entered host/port. The advertised TXT records expose the agent version, REST API base path (`/api/v1`), and server name. Enabled by default; controllable via `--discovery-enabled` / `DISCOVERY_ENABLED` and an optional instance-name override `--discovery-service-name` / `DISCOVERY_SERVICE_NAME`, plus a `discovery:` section in the YAML config file. Advertising is best-effort and coexists with Unraid's existing `avahi-daemon`. Implemented with `github.com/grandcat/zeroconf` (DNS-SD/Avahi-compatible). The agent detects the primary LAN IPv4 (default-route source address) and advertises only that reachable address, so it never publishes unreachable Docker/`virbr0` bridge IPs on multi-interface Unraid hosts. Verified live on Unraid: a separate LAN machine browsed `_unraid-mgmt-agent._tcp`, resolved the instance to `.local:8043` with the correct TXT records, and reached the advertised LAN IP. - **Agent Skill for AI coding agents** (#121) — added a portable Markdown knowledge pack under `skills/unraid-management-agent/` following the open [Agent Skills standard](https://agentskills.io), teaching agents how to monitor and control Unraid through the agent's MCP server (121 tools, 5 resources, 6 diagnostic prompts) and REST API. Installable via `npx skills add ruaan-deysel/unraid-management-agent`, as a Claude Code plugin (`.claude-plugin/marketplace.json`), or as a claude.ai/Claude Desktop skill zip. The skill bundles the full tool catalog (with read/write + destructive flags), connection setup for every MCP client, the diagnostic prompts/resources, the REST surface for non-MCP clients, and request → tool workflows. - **ChatGPT Custom GPT Actions** (#121) — added a curated OpenAPI 3.0 schema (`docs/integrations/chatgpt/openapi-actions.yaml`, ~30 endpoints with `operationId`s and `x-openai-isConsequential` on state-changing operations) plus a setup guide so ChatGPT can monitor/control Unraid via REST Actions. - **AI integrations index** (`docs/integrations/README.md`) and a Claude integration guide (`docs/integrations/claude/README.md`). ### Changed - Upgraded the transitive `github.com/miekg/dns` dependency from `v1.1.27` (2020) to `v1.1.72` while adding the zeroconf dependency. - Updated `docs/integrations/mcp.md` counts to the current 121 tools / 74 read-only, replaced the stale 3-prompt list with the actual 6 diagnostic prompts, and cross-linked the new Claude/ChatGPT integration guides. --- ## [2026.06.01] - 2026-06-05 ### Added - **Remote share mount/unmount control** (issue #115; resolves the mount/unmount toggle ask in ruaan-deysel/ha-unraid-management-agent#79) — SMB/NFS remote shares can now be mounted and unmounted by source. Added the REST endpoints `POST /api/v1/unassigned/remote-shares/mount` and `.../unmount` (body `{"source": "//server/share"}`), the MCP `remote_share_action` tool, and an MQTT Home Assistant **switch** per share. Operations are delegated to the plugin's `rc.unassigned` control script so credentials, protocol versions, and mount options match the Unraid web UI. Sources are validated (`ValidateRemoteShareSource`) and passed as discrete exec arguments (never via a shell). Verified live on Unraid: mount via REST and unmount via MCP both toggled a real CIFS mount, with the API reflecting the change. - **Configured-but-unmounted remote shares are now reported** (issue #115; resolves ruaan-deysel/ha-unraid-management-agent#79) — the collector parses the plugin's `samba_mount.cfg` and merges it with `/proc/mounts`, so shares that are configured but not currently mounted appear with `status: "unmounted"` (plus their `auto_mount`/`read_only` metadata), enabling mount/unmount toggles in Home Assistant for offline shares. - **MCP `get_remote_shares` tool** (issue #115) — a dedicated read-only tool returning SMB/NFS/ISO remote share mount status and space usage. - **MQTT Home Assistant discovery for remote shares** (issue #115) — each remote share now publishes a `mounted` binary sensor plus usage / used / free capacity sensors, mirroring the existing unassigned-device entities. - **Agent Core (Phase 3 — Memory, Planner, Learning, Multi-turn Chat):** - **Episodic memory with semantic recall:** when a session finishes the agent writes an incident record (signature, goal, outcome, summary, action tags) to `agent_memory.json`. At the start of each new session the agent recalls the most relevant past incidents via keyword / tag matching (top-K, default 3) and injects them as context before the first LLM call. Active operator preferences are included in the same context injection. Controlled by new config fields `memory_enabled` (default `true`), `max_incidents` (default `200`), and `recall_top_k` (default `3`). - **Goal-decomposition planner:** operator-initiated sessions run one extra LLM call before the ReAct loop that decomposes the goal into a short ordered plan (`sess.Plan`, list of `{intent, tool}` steps) and injects a plan summary into the transcript. The call is best-effort — a planner failure never aborts the session. - **Suggest-not-mutate learning:** the agent can call two new read-only tools during a session: `propose_preference` (stores a `PENDING` autonomy / runbook preference) and `propose_runbook` (stores a proposed runbook in `agent_runbooks.json` alongside the static reviewed catalogue). Proposals never take effect until an operator confirms them. A confirmed preference of subject type `auto_approve_tool` (where `subject` is a tool name) causes the policy gate to auto-approve calls to that tool (the forbid-list still wins). - **Multi-turn chat (SendMessage):** `POST /api/v1/agent/sessions/{id}/messages` with body `{"message":"…"}` continues a completed or failed session with a new operator message and re-runs the ReAct loop. The conversation history is preserved across turns. - **New REST endpoints:** - `POST /api/v1/agent/sessions/{id}/messages` — send a follow-up message to a finished session. - `GET /api/v1/agent/memory` — returns `{incidents, preferences}` from the in-memory store. - `POST /api/v1/agent/preferences/{id}/confirm` — confirm (activate) a pending learned preference by ID. - **New MCP tools:** `agent_send_message`, `agent_get_memory`, `agent_confirm_preference` — exposing multi-turn chat, memory inspection, and preference confirmation to external MCP clients. - **New `agent_config.json` fields:** `memory_enabled` (bool, default `true`), `max_incidents` (int, default `200`), `recall_top_k` (int, default `3`). - **Agent Core (Phase 2 — Autonomy & Approval):** - **Event-driven triggers:** the alerting Engine and watchdog Runner publish a `dto.AgentWakeEvent` to the `agent_wake` typed pub-sub topic whenever an alert fires or a health check fails. The agent subscribes at daemon startup (before collectors start) and spawns autonomous investigation sessions automatically. Wake events are debounced (default 30 s), deduplicated by subsystem, and rate-limited by a per-subsystem cooldown (default 300 s). A concurrency cap (default 2) prevents more than a configurable number of parallel autonomous sessions. - **Approval gate (pause / resume):** when the agent proposes a tool whose risk tier maps to `approve`, the session pauses with status `awaiting_approval` and a `pending_approval` object (`action_id`, `tool`, `args`, `risk_tier`, `reason`). The full conversation transcript is persisted to disk so the session survives a daemon restart. Approving or denying the action resumes the loop from where it left off. An automatic TTL sweeper (default 3 600 s / 1 hour) denies any approval that has not been resolved within the configured window. - **Non-overridable forbid-list:** a hard-coded list of irreversible disk operations (`format_disk`, `clear_parity`, `disable_parity`, `partition_disk`, `delete_array_disk`) that the agent will describe but the gate **never** executes, even with explicit operator approval. The list is also configurable via the `forbid_list` field in `agent_config.json`. - **REST endpoints:** `POST /api/v1/agent/sessions/{id}/approve` (body `{"action_id":"…","approve":true|false}`) approves or denies a pending action and resumes the session; `POST /api/v1/agent/sessions/{id}/cancel` cancels a running or awaiting-approval session. - **MCP agent tools:** `agent_start_session`, `agent_get_session`, `agent_list_sessions`, `agent_approve_action` — exposing the full agent lifecycle to external MCP clients. - **New WebSocket events** on the `agent_stream` topic: `agent_approval_required` (session paused, pending action details in payload) and `agent_session_cancelled`. - **OpenAI-compatible LLM providers:** in addition to `"anthropic"`, the `provider` field now accepts `"openai"`, `"openrouter"`, and `"gemini"`. OpenRouter uses `https://openrouter.ai/api/v1/chat/completions` by default; Gemini uses `https://generativelanguage.googleapis.com/v1beta/openai/chat/completions`. All providers read the API key from `UMA_AGENT_API_KEY`. Example free model via OpenRouter: `openai/gpt-oss-20b:free`. - **New `agent_config.json` fields:** `wake_debounce_secs` (default `30`), `wake_cooldown_secs` (default `300`), `max_concurrent_sessions` (default `2`), `approval_ttl_secs` (default `3600`), `forbid_list` (`[]string`). - **Agent Core (Phase 1):** embedded autonomous operator with a pluggable LLM provider (Anthropic), a risk-tiered tool registry (read-only + low-risk auto-execute; high-risk reserved for approval), a bounded ReAct reasoning loop with iteration/token/deadline caps, JSON-persisted sessions, REST endpoints under `/api/v1/agent`, and a WebSocket `agent_stream` event feed. Disabled by default; opt-in via `agent_config.json` (`enabled: true`) and the `UMA_AGENT_API_KEY` environment variable. - **Continuous Docker container update detection** — a new background `docker_update` collector (default 6 h interval, staggered start, registry-rate-limit-safe) performs periodic digest comparisons for all running containers without blocking normal Docker polling. - `update_status`, `update_available`, and `update_checked` fields exposed on every container in `GET /api/v1/docker`, `GET /api/v1/docker/{id}`, MCP `list_containers`, and MCP `get_container_info`. - `GET /api/v1/docker/updates` now serves the cached result instantly (no live check on request; populated by the background collector). - `POST /api/v1/docker/updates/refresh` triggers an immediate re-check outside the normal schedule and publishes the result via the event hub. - MCP tool `refresh_container_updates` exposes the same on-demand refresh to AI agents. - `ContainerUpdatesAvailable` alerting metric — counts containers with an update ready; usable in alert rule expressions. - Opt-in startup notification when new container updates are detected: `--docker-update-notify` flag / `DOCKER_UPDATE_NOTIFY=true` env variable. - **Trend / predictive alerting** — in-memory ring-buffer `MetricsHistory` samples key metrics continuously. New alerting-expression fields: - `ArrayFillETAHours` — hours until the array is full at current fill rate - `MaxDiskFillETAHours` — hours until the fastest-filling individual disk is full - `CPUTempSlopePerMin` — CPU temperature trend (°C / min) - `MaxDiskTempSlopePerMin` — steepest disk temperature rise across all disks (°C / min) - `MaxContainerRestartsPerHour` — highest container restart rate over the sampled window - `MaxReallocatedSectors` — maximum reallocated sector count across all array disks - `MaxPendingSectors` — maximum pending sector count across all array disks - `DiskErrorsIncreasing` — `true` when any disk's error count is trending upward - Disk SMART attributes (`Reallocated_Sector_Ct`, `Current_Pending_Sector`) now collected via `smartctl -A` and surfaced in the alerting environment. - **Alert rule templates** — `GET /api/v1/alerts/templates` and MCP tool `list_alert_templates` return five curated, disabled-by-default rule templates using trend/predictive metrics. Users can review, copy, and enable them in their rule config. - **One-click alert template enable** — `POST /api/v1/alerts/templates/{id}/enable` and MCP tool `enable_alert_template` instantiate and enable an alert rule directly from a template ID (e.g. `tmpl-array-fill`) in a single call. Idempotent — re-posting updates the existing rule without duplication. Defaults notification channels to `["unraid"]` (Unraid built-in notifications) when no `channels` body is provided. - **Container network I/O** — `network_rx_bytes`, `network_tx_bytes`, `network_rx_bytes_per_sec`, and `network_tx_bytes_per_sec` fields now populated on every container in `GET /api/v1/docker` and `GET /api/v1/docker/{id}` via sampling from `/proc//net/dev`. `restart_count` field also added to each container. - **Docker networks** — new `GET /api/v1/docker/networks` endpoint and MCP tool `list_docker_networks` expose all Docker networks with driver, scope, IPAM subnet / gateway, and the list of connected container names. - **Continuous plugin update detection** — a new background `plugin_update` collector (configurable via `INTERVAL_PLUGIN_UPDATE`, default 6 h) checks all installed plugins for updates and caches the result. `POST /api/v1/plugins/updates/refresh` and MCP tool `refresh_plugin_updates` trigger an immediate re-check and publish the result via the event hub. `PluginUpdatesAvailable` alert metric counts plugins with a pending update. - **OS update availability (local-only)** — new `GET /api/v1/os/update` endpoint and MCP tool `get_os_update` return the cached OS update status sourced entirely from local files (`/etc/unraid-version`, `/tmp/plugins/update/`). No outbound network calls are made. A background `os_update` collector refreshes this on a configurable interval (`INTERVAL_OS_UPDATE`, default 24 h). Status values: `up_to_date`, `update_available`, `unknown`. - **Mover status** — new `GET /api/v1/mover` endpoint and MCP tool `get_mover_status` expose the cached mover state: whether it is currently active, the cron schedule from `var.ini`, and last-run statistics (start/finish timestamps, duration, files moved, bytes moved) parsed from `/var/log/mover.log`. Refreshed by the new `mover` collector (`INTERVAL_MOVER`, default 5 min). - **AI remediation toolkit:** - **System health report** — `GET /api/v1/health/report` and MCP tool `system_health_report` aggregate health signals from the array, disks, containers, and firing alerts into a prioritised findings list with recommended actions. The MCP tool is read-only by default; passing `confirm: true` together with an `actions` list (from a previous report) executes supported actions (`start/stop/restart_container`, `start/stop/restart/force_stop_vm`) via the remediation executor. - **Metric history query** — `GET /api/v1/metrics/history?metric=&entity=` and MCP tool `query_metric_history` return all buffered ring-buffer samples plus summary statistics (slope per second, min, max, average, last value) for any tracked metric. Global metrics: `cpu_temp`, `array_used_pct`. Per-entity metrics: `disk_temp`, `disk_used_pct`, `disk_errors`, `reallocated`, `pending`, `restart_count`. - **Runbook catalogue** — MCP tool `list_runbooks` lists reviewed remediation runbooks. `run_runbook` dry-runs or executes a named runbook (requires `confirm: true` to execute). `find_root_cause` correlates cached system signals (CPU, array, parity, disk temperatures, containers) to surface the most likely root causes of degraded performance or health. - **New collectors:** `docker_networks`, `plugin_update`, `os_update`, `mover` — each follows the standard collector pattern with panic recovery, configurable polling interval, and WebSocket / event-hub publishing on each update. - **New environment variables / CLI flags for collector intervals:** `INTERVAL_DOCKER_NETWORKS`, `INTERVAL_PLUGIN_UPDATE`, `INTERVAL_OS_UPDATE`, `INTERVAL_MOVER`. ### Changed - **More robust `parity_valid` determination** (issues #98, #114) — array parity validity now corroborates the `var.ini` `sbSynced` signal against the parity-checks log: when the array is started with parity disks and the most recent parity check/sync completed successfully (exit 0, zero errors), parity is reported valid even on Unraid versions that omit or zero out `sbSynced`. This corroboration is additive (it can only confirm validity, never mask sync errors). Added comprehensive debug logging of every signal feeding the decision (`sbSynced`, `sbSynced2`, `sbSyncErrs`, `sbSyncExit`, `mdNumInvalid`, `mdResync`, `numParityDisks`, state) to aid future diagnosis. - **`GET /api/v1/docker/updates`** now returns the cached result immediately instead of performing a live registry check on each request; use the new `POST /api/v1/docker/updates/refresh` to force an on-demand re-check. - **Runtime collector interval cap** raised from 3 600 s to 86 400 s (24 h) to accommodate the new `docker_update` collector's default 6 h schedule. ### Fixed - **Remote shares (SMB/NFS) never reported** (issue #115; resolves ruaan-deysel/ha-unraid-management-agent#83 and ruaan-deysel/ha-unraid-management-agent#79) — the unassigned-devices collector's `parseSMBMounts()` was a stub that always returned an empty list, and NFS mounts were not parsed at all, so `remote_shares` was permanently empty across the REST API (`/api/v1/unassigned`, `/api/v1/unassigned/remote-shares`), MQTT, and MCP. As a result Home Assistant could never create binary sensors for remote shares. The collector now parses `/proc/mounts` for `cifs`/`smb3`/`smbfs` and `nfs`/`nfs4` filesystems mounted under `/mnt/remotes/` and `/mnt/disks/`, populating server/share/export fields, read-only status, and capacity (size/used/free/usage via `statfs`). Octal-escaped mount fields (e.g. spaces) are decoded. ISO loop mounts are still detected. - **MCP `get_unassigned_devices` hid remote shares** (issue #115) — the tool returned "No unassigned devices found" whenever the local device list was empty, even when remote shares were present. It now returns data when either `Devices` or `RemoteShares` is non-empty. - **MCP `get_parity_history` always returned "No parity check history available"** (issue #114) — the cache layer's `GetParityHistoryCache()` was a stub that unconditionally returned an empty result, so the MCP tool (and the parity section of `system_health_report` / `get_diagnostic_summary`) never reported any history even when `/boot/config/parity-checks.log` was populated. It now loads and caches the real parity-checks log (60 s TTL), matching the `GET /api/v1/array/parity-check/history` REST endpoint. Verified live: REST, MCP, and the on-disk log all report identical record counts. ### Security - **Upgraded Go toolchain 1.26.3 → 1.26.4** — picks up the standard-library security fixes in Go 1.26.4 (released 2026-06-02): CVE-2026-27145 (`net/textproto`, reached via `net/http` response-header parsing — relevant to the daemon's outbound HTTP), CVE-2026-39822 (`crypto/x509`), and CVE-2026-42504 (`mime`). Bumped the `go` directive in `go.mod` and the `setup-go` version in the release workflow. ## [2026.06.00] - 2026-05-30 ### Added - **Unraid 7.3 support — new data exposed across the API/MQTT/MCP:** - **Chassis serial number** in `/api/v1/hardware` (`chassis` block: manufacturer, type, version, serial, asset tag) via sysfs DMI with dmidecode (type 3) fallback. New HA sensor `Hardware: Chassis Serial`. - **TPM presence/version** in `/api/v1/hardware` (`tpm` block) from `/sys/class/tpm/tpm0` — supports Unraid 7.3 TPM-based licensing. New HA binary sensor `Hardware: TPM Present`. - **Boot device / boot pool** in `/api/v1/hardware` (`boot` block: device type usb vs internal, backing device, filesystem, ZFS boot pool) — Unraid 7.3 internal-boot support. New HA sensor `Hardware: Boot Device Type`. - **ZFS corrupted files** per pool (`corrupted_files`) parsed from `zpool status -v` (ZFS 2.4.1 surfaces these without a scrub), plus `is_boot_pool` flag. New HA sensor `ZFS: Corrupted Files`. - **ZFS configured ARC max** (`configured_max_bytes`) from `/sys/module/zfs/parameters/zfs_arc_max` (0 = auto) — Unraid 7.3 first-class tunable. New HA sensor `ZFS ARC: Configured Max`. - **Docker container MAC address** (`mac_address`) from `docker inspect` (Docker 29 / Unraid 7.3 fixed-MAC support). New per-container HA sensor. - **Per-process disk I/O** via new `GET /api/v1/processes/io` and MCP tool `list_process_io` — top processes by I/O rate sampled natively from `/proc//io` (lighter than spawning iotop-c, no always-on cost). - **New alert-rule fields:** `ZFSCorruptedFiles`, `BootPoolHealthy`, `BootPoolHealth` (degraded ZFS boot pool / corrupted files alerting). - **Swap memory metrics in `/api/v1/system`** — the system collector now reports `swap_total_bytes`, `swap_used_bytes`, `swap_free_bytes`, and `swap_usage_percent` parsed from `/proc/meminfo`, plus the kernel `swappiness` tunable from `/proc/sys/vm/swappiness` (`-1` when unavailable). Addresses the Home Assistant integration swap-sensor request (ha-unraid-management-agent #45). - **Swap & swappiness Home Assistant sensors** — new MQTT discovery sensors (`swap_usage`, `swap_used`, `swap_free`, `swap_total`, `swappiness`). - **Swap fields available to the alerting engine** — `SwapUsedPct`, `SwapTotalBytes`, `SwapUsedBytes`, and `SwapFreeBytes` can now be used in alert rule expressions. - **Home Assistant notification event entity** — a new MQTT `event` entity (`notifications/event` topic) fires a Home Assistant event for each new Unraid notification, exposing the full notification details (id, title, subject, description, importance, type, link, timestamp) as event attributes. The existing backlog is seeded silently on startup so a restart does not replay old notifications. ### Security - **Fixed reachable vulnerability GO-2026-5013** — bumped `golang.org/x/crypto` v0.51.0 → v0.52.0 (byte-arithmetic underflow/panic in `golang.org/x/crypto/ssh`, reachable via libvirt `ConnectToURI` → `ssh.Dial` in the VM collector). `govulncheck ./...` now reports zero vulnerabilities. ### Changed - **Dependencies bumped** — `github.com/nicholas-fedor/shoutrrr` → v0.15.1, `golang.org/x/net` → v0.55.0, `golang.org/x/sys` → v0.45.0, `golang.org/x/crypto` → v0.52.0; `go mod tidy` run. `go vet` clean. - **Unraid 7.3 regression checks (no change required):** verified RAM reporting is unaffected by the 7.3 dmidecode unit-label change (memory device size is a passthrough string; system memory comes from `/proc/meminfo`), and confirmed disk SMART polling already uses `smartctl -n standby` so routine collection never wakes spun-down drives (aligns with 7.3's "don't spin up the array on reads" fix). ## [2026.05.00] - 2026-05-16 ### Changed - **Go version upgraded to 1.26.3** — Latest stable Go release with improved performance (Green Tea GC, ~30% faster cgo, ~2x faster `io.ReadAll`) - **All dependencies updated to latest versions** — 16 direct dependencies and 30+ transitive dependencies bumped to their latest compatible versions including: - `go.opentelemetry.io/*` → v1.43.0 - `github.com/prometheus/common` → v0.67.5 - `github.com/prometheus/procfs` → v0.20.1 - `golang.org/x/*` tooling packages (crypto, mod, net, sys, term, tools) - `github.com/go-openapi/swag/*` → v0.26.0 - All other dependencies audit-tested with `govulncheck` — zero vulnerabilities found ### Fixed - **ZFS cache/pool disk usage in `/api/v1/disks`** — disk collection now resolves ZFS-backed cache and pool members to their owning zpool and uses `zpool list` capacity data instead of `statfs()` on `/mnt/`, fixing near-zero usage on root datasets and `null` usage for mirrored members like `cache2` Thanks for the contribution @laurensguijt ## [2026.04.01] - 2026-04-10 ### Fixed - **Individual GPU metrics for multi-vendor systems** — GPU indices are now globally unique across all vendors (Intel → NVIDIA → AMD ordering), fixing MQTT topic collisions and Home Assistant entity conflicts when multiple GPU vendors are present (e.g., Intel iGPU + NVIDIA discrete GPUs). Previously, each vendor assigned indices starting from 0, causing `unraid/gpu/0` to be shared by different GPUs. Closes #105 - **Stabilise ROCm GPU ordering** — AMD GPU collection via `rocm-smi` now sorts card identifiers numerically before processing, preventing non-deterministic index assignment across collection cycles - **Replace direct `exec.Command` usage** — replaced 2 occurrences in `unassigned.go` and 1 in `probes.go` with `lib.ExecCommandOutput` / `lib.ExecCommandOutputWithContext` wrappers for consistent command execution and error handling - **Race condition in DockerCollector** — added `sync.Mutex` to protect concurrent access to the `prevCPU` map in `getCPUFromCgroups` and `pruneStaleSnapshots` - **CORS missing PATCH method** — added `PATCH` to `Access-Control-Allow-Methods` in CORS middleware so preflight requests for `PATCH /api/v1/collectors/{name}/interval` succeed - **WebSocket localhost alias matching** — added `isLocalhost()` equivalence check in WebSocket `CheckOrigin` for consistency with CSRF middleware (allows `localhost`/`127.0.0.1`/`::1` to match interchangeably) - **Zombie processes from background scripts** — replaced `proc.Release()` with `go proc.Wait()` in `executeScriptBackground` to properly reap child processes - **lsblk stderr contaminating JSON** — added `ExecCommandStdout` helper and switched `getDeviceInfo` in unassigned collector to use stdout-only output, preventing stderr warnings from corrupting JSON parsing - **Ping target validation** — added `ValidateHostOrIP` validator and applied it in `probePing` to reject empty, flag-prefixed, or malformed targets before execution ### Security - **Sanitize verbose error responses** — Removed raw error details from 40+ REST API 500 Internal Server Error responses. Internal errors are now logged server-side only; clients receive generic error messages without implementation details (OWASP A05:2021) - **Upgrade OpenTelemetry to v1.41.0** — Fixes CVE-2026-29181 (CWE-770 uncontrolled resource consumption) in go.opentelemetry.io/otel v1.39.0 - **Add global rate limiting middleware** — New rate limiter (10 req/s, burst of 20) mitigates denial-of-service and brute-force attacks on REST API endpoints - **Restrict CORS default** — CORS headers are no longer set when `CORS_ORIGIN` is not explicitly configured. Previously defaulted to `Access-Control-Allow-Origin: *` which allowed any website to make cross-origin API requests. Set `CORS_ORIGIN` explicitly to restore cross-origin access if needed - **Warn on insecure MQTT TLS** — Log a warning at startup when MQTT `InsecureSkipVerify` is enabled to flag insecure TLS configurations - **Fix shell injection in userscripts controller** — removed the `sh -c` + `fmt.Sprintf` command-construction pattern in favor of direct argument passing to eliminate CWE-78 command injection risk - **WebSocket origin validation** — added per-request origin checking that validates the Origin header host against the request Host; added 64 KB `ReadLimit` to prevent message-size DoS - **Security headers middleware** — added `X-Content-Type-Options: nosniff`, `X-Frame-Options: DENY`, `X-XSS-Protection`, `Content-Security-Policy`, `Referrer-Policy`, and `Permissions-Policy` headers to all HTTP responses - **CSRF origin validation middleware** — validates Origin header on state-changing requests (POST/PUT/PATCH/DELETE) with localhost-aware matching - **Request body size limit** — added 1 MB `MaxBytesReader` middleware to prevent request body DoS across all endpoints - **HTTP server timeout hardening** — added `ReadHeaderTimeout` (10 s) and `IdleTimeout` (120 s) to mitigate slowloris-style attacks - **Null byte injection protection** — added CWE-158 null byte checks to `ValidateShareName`, `ValidateUserScriptName`, `ValidatePluginName`, and `ValidateSnapshotName` - **Consistent validation error reporting** — refactored `ValidateLogFilename` from `bool` return to `error` return with descriptive messages matching other validators ## [2026.04.00] - 2026-04-08 ### Security - **Fix shell injection vulnerability in start script** — replaced unsafe `bash -c` with unquoted variable interpolation with `env(1)` for safe variable passing; added input sanitization helpers (`sanitize_int`, `sanitize_csv`, `sanitize_str`, `sanitize_bool`) to validate all config values - **Sanitize all config values** — integer-only validation for ports/intervals, boolean allowlist for flags, shell metacharacter stripping for freeform strings, LOG_LEVEL allowlist ### Added - **Log action handler in exec.php** — new `log` action returns last 20 log lines for display in settings UI - **Default config additions** — added `LOG_LEVEL` and `MQTT_CLIENT_ID` defaults to `default.cfg` - **Diagnostic Logging System** ([#102](https://github.com/ruaan-deysel/unraid-management-agent/issues/102)): - **Structured diagnostic logger** — JSON Lines format with lumberjack rotation (5 MB max, 1 backup) - **Correlation IDs** — UUID-based request/operation correlation via context propagation - **Sensitive data redaction** — regex-based redaction for passwords, bearer tokens, Shoutrrr URLs, webhooks, CSRF tokens; reflection-based struct/map redaction for sensitive field names - **Diagnostic bundle CLI** — `diagnostics` command collects system state, array status, containers, VMs, network info, logs, and configuration into a timestamped ZIP archive - **Diagnostic bundle service** — collects metadata, system state from `/proc`, array/disk info, Docker/VM listings, network interfaces, agent logs, and redacted configuration - New DTOs: `DiagnosticLogEntry`, `DiagnosticBundle`, `BundleMetadata`, `BundleSystemState`, `BundleArrayStatus`, `BundleDisk`, `BundleContainer`, `BundleVM`, `BundleNetwork`, `BundleLogs`, `BundleConfiguration` ### Fixed - **Concurrent map access in DiskCollector** ([#100](https://github.com/ruaan-deysel/unraid-management-agent/pull/100)): - Added `sync.Mutex` to protect shared map access during disk data collection ## [2026.03.06] - 2026-03-30 ### Added - **System Tuning (Tips & Tweaks)** — new collector, controller, REST endpoints, and MCP tools: - **Turbo Boost**: read/write Intel Turbo Boost and AMD Performance Boost via sysfs - **Disk Cache**: read/write `vm.dirty_*` kernel parameters (background ratio, ratio, writeback, expire) - **Inotify Limits**: read/write `fs.inotify.*` kernel parameters (max watches, instances, queued events) - **NIC Offloads**: read hardware offload feature states via `ethtool -k` (rx/tx checksumming, scatter-gather, TSO, GSO, GRO, LRO, VLAN) - **NIC Ring Buffers**: read ring buffer sizes via `ethtool -g` (RX/TX max and current) - REST API: `GET /api/v1/tuning`, `POST /api/v1/tuning/turbo`, `POST /api/v1/tuning/disk-cache`, `POST /api/v1/tuning/inotify` - MCP tools: `get_tuning_status`, `set_turbo_boost`, `set_disk_cache`, `set_inotify_limits` - Tuning controller restores original values on agent shutdown - Configurable collection interval (default 120s, env `INTERVAL_TUNING`) ### Fixed - **Agent keeps entering stopped state due to unrecovered panics** ([#99](https://github.com/ruaan-deysel/unraid-management-agent/issues/99)): - Added `LogPanicWithStack` helper for stack-trace-enriched panic logging across all recovery blocks - Added missing panic recovery to Docker, GPU, and VM collectors (previously had none) - Fixed Notification collector: restructured from top-level-only recovery to per-iteration recovery - Added top-level crash recovery to Alerting engine and Watchdog runner - Wrapped all Orchestrator goroutines (alert engine, watchdog, HTTP server, MQTT, STDIO mode) with panic recovery - Upgraded all existing collector recovery blocks to use `LogPanicWithStack` for better crash diagnostics - Added lifecycle logging: "Agent startup complete" and "Shutdown complete" messages ## [2026.03.05] - 2026-03-30 ### Fixed - **`parity_valid` returns false on Unraid 7.2.4 when parity is actually valid** ([#98](https://github.com/ruaan-deysel/unraid-management-agent/issues/98)): - Added `mdNumInvalid` fallback for parity validity when `sbSynced` is missing or zero - A started array with parity disks and zero invalid disks is now correctly reported as parity-valid - Fixes false "Parity Invalid" repair notifications in the Home Assistant integration - Added debug logging of `sbSynced`, `mdNumInvalid`, and `sbSyncErrs` values during array collection - **CPU temperature sensor returns incorrect/stuck value (127.5°C) on some hardware** ([#97](https://github.com/ruaan-deysel/unraid-management-agent/issues/97)): - CPU and motherboard temperature extraction now uses `IsPlausibleTempC()` to reject readings outside the -40°C to 125°C range - Filters out stuck 127.5°C TjMax sentinel values reported by some hwmon sensors on Intel systems - Motherboard temp check unified to use the same plausibility function instead of a hardcoded 0–100°C range ## [2026.03.04] - 2026-03-29 ### Added - **Full fan control system** — monitor and control chassis fans via REST API, WebSocket, MCP, and Prometheus: - New `GET /fans` endpoint returning per-fan RPM, PWM %, mode, and controllability - `POST /fans/speed` — set fan speed by percentage (manual mode) - `POST /fans/mode` — switch between off/manual/automatic modes - `POST /fans/profile` — assign a temperature-responsive fan curve profile to a fan - `POST /fans/profile/create` — create custom fan curve profiles with temperature→speed points - `POST /fans/defaults` — restore all fans to their original state - `PUT /fans/config` — enable/disable fan control and update safety thresholds - Hwmon-based fan discovery scanning `/sys/class/hwmon` for all fan channels and PWM controls - Fan curve engine with linear interpolation between temperature→speed curve points - Built-in profiles: silent, balanced, performance, full-speed - Safety guard with emergency full-speed on critical temperature, fan stall detection, minimum speed enforcement, and original state capture/restore on shutdown - IPMI fan provider stub for future BMC-based fan control - Fan control collector publishing `fan_control_update` events to the event bus - WebSocket broadcast of real-time fan status changes - 6 MCP tools: `get_fan_status`, `set_fan_speed`, `set_fan_mode`, `create_fan_profile`, `assign_fan_profile`, `restore_fan_defaults` - 10 Prometheus gauges: `unraid_fan_rpm`, `unraid_fan_pwm_percent` per fan - MQTT Home Assistant discovery for per-fan sensor entities - Input validation for fan IDs, speed percentages, mode values, and profile names - **MQTT Home Assistant sensor coverage for network throughput rates** ([#96](https://github.com/ruaan-deysel/unraid-management-agent/pull/96)): - New `RxBytesPerSec` and `TxBytesPerSec` fields in `NetworkInfo` DTO for per-interface throughput - Delta-based rate calculation with mutex-protected previous sample state - Stale interface entries pruned automatically when interfaces disappear - **MQTT Home Assistant discovery for fan sensors** ([#96](https://github.com/ruaan-deysel/unraid-management-agent/pull/96)): - Dynamic per-fan HA sensor entities discovered from system fan data - Fan discovery goroutine publishes entities on first `PublishSystemInfo` call - **MQTT Home Assistant discovery for NUT UPS, hardware, registration, unassigned devices, and ZFS** ([#96](https://github.com/ruaan-deysel/unraid-management-agent/pull/96)): - 7 new MQTT Publish methods: NUT, Hardware, Registration, Unassigned, ZFS Datasets, ZFS Snapshots, ZFS ARC Stats - 7 new `mqttBind` entries in orchestrator for new event topics - HA discovery entities for NUT UPS sensors, hardware info, registration/license, per-unassigned-device sensors, per-ZFS-dataset sensors, ZFS snapshot aggregates, and ZFS ARC stats - **Context-aware shell command execution** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - New `ExecCommandOutputWithContext()` in `daemon/lib/shell.go` for commands that need timeout/cancellation support - **ZFS per-share sizing** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - Share collector now queries ZFS dataset sizes via `zfs list` for `useCache=only` shares - 60-second timeout prevents a hung `zfs list` from blocking the share collector goroutine ### Fixed - **Share sizes reported at ~50% of actual size** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - Unraid's share `.ini` files store sizes in KiB, but the share collector multiplied by 1 (treating as bytes) - Changed to multiply by 1024 so `free_bytes` and `used_bytes` report correct values - **Notification collector reads paths from dynamix.cfg** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - `ResolveNotificationDirs()` now reads the Dynamix notification directory from `dynamix.cfg` at startup - Notification controller resolves paths via `collectors.ResolveNotificationDirs` at `init()` so create/archive/delete target the correct directories - Dynamix `"normal"` importance now correctly maps to `"info"` in notification counts - **Dark mode UI fixes for plugin settings page** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - Plugin page now uses proper Unraid CSS variables for dark theme compatibility - **Log spammed with UPS and fan warnings** ([#95](https://github.com/ruaan-deysel/unraid-management-agent/issues/95)): - Fan speed "no fan sensors found" downgraded from `Warning` to `Debug` — no longer spams logs every 15 seconds on systems without fan sensors - NUT UPS fallback failure downgraded from `Warning` to `Debug` - Fixed broken printf format strings in 19 logger calls across system, ZFS, and UPS collectors that used structured-logging style args (`"msg", "key", value`) instead of printf verbs (`"msg: %v", value`), causing `%!(EXTRA ...)` garbage in log output - **UPS collector panic recovery** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - Periodic `Collect()` call now wrapped in defer/recover to prevent a panic from killing the goroutine - **MQTT nil dereference guard** ([#96](https://github.com/ruaan-deysel/unraid-management-agent/pull/96)): - `PublishSystemInfo` now guards against nil `SystemInfo` to prevent panic on first publish before data is available - **Fan speed parsing improved for `sensors -u` format** ([#96](https://github.com/ruaan-deysel/unraid-management-agent/pull/96)): - Fan RPM parsing now uses `ParseFloat` for the raw `sensors -u` output format - Hwmon fan labels simplified to `"Fan %d"` format - Zero RPM fan channels (no fan connected) are skipped - **Bogus sensor filtering hardened against unreliable motherboard hardware**: - Fan RPM readings above 25 000 RPM (`MaxPlausibleRPM`) are treated as non-detected — fixes fan5 reporting 56 784 RPM from a bogus hwmon sensor - Temperature sensors with known-unreliable labels (AUXTIN, SYSTIN, intrusion) are now skipped in `ReadMaxHwmonTemp()` — prevents phantom readings from unpopulated Nuvoton/ITE chipset headers triggering false emergency full-speed - Plausible temperature range tightened from 150 °C to 125 °C to reject I²C/SMBus "not connected" sentinel values (127–128 °C) - Fan curve per-sensor temperature reads now validated with `IsPlausibleTempC()` before use in speed interpolation ### Changed - **APC UPS fallback log level** ([#93](https://github.com/ruaan-deysel/unraid-management-agent/pull/93)): - `apcaccess` failure message downgraded from `Warning` to `Debug` to reduce log noise on systems without APC UPS ## [2026.03.03] - 2026-03-18 ### Fixed - **FileWatcher race condition causing silent agent exits every 1–4 hours** ([#84](https://github.com/ruaan-deysel/unraid-management-agent/issues/84)): - Moved `fw.Close()` inside the goroutine running `fw.Run()` in array, disk, and share collectors so the fsnotify channels are no longer closed while the watcher loop is still selecting on them - Added warning-level logging in `filewatcher.go` when the events or errors channel closes unexpectedly, making any future silent exits visible in the log - **Removed dead `ENABLE_UPS` and `ENABLE_GPU` config flags from PLG template** ([#83](https://github.com/ruaan-deysel/unraid-management-agent/issues/83)): - The flags had no effect — collectors always started regardless of their value - Setting `INTERVAL_UPS=0` or `INTERVAL_GPU=0` (or any collector interval to `0`) is the supported way to disable collectors, now documented in the config template comment ## [2026.03.02] - 2026-03-16 ### Fixed - **WebSocket client eviction no longer mutates the hub under a read lock**: - Broadcast now snapshots clients before sending and removes stale clients under an exclusive lock, avoiding concurrent map mutation under load - **Unassigned device filesystem sizing no longer shells out to `df`**: - Mounted partition and remote share usage now uses native `statfs` calls, reducing process overhead and removing subprocess findings - **Collector runtime shutdown state is now cleared explicitly**: - Collector cancellation is released on disable, restart, stop-all, and collector exit, with regression coverage for runtime state cleanup - **MQTT QoS is normalized before publish/subscribe operations**: - Invalid QoS values now fall back safely to `0` instead of flowing through unchecked conversions - **Alert message formatting avoids unnecessary temporary strings**: - Dispatcher now writes formatted content directly to the builder - **Parity check history endpoint no longer returns invalid JSON on systems without parity** ([#82](https://github.com/ruaan-deysel/unraid-management-agent/issues/82)): - `parseSpeed()` now guards against `NaN` and `Inf` float64 values that `strconv.ParseFloat` accepts but `encoding/json` cannot serialize - Systems that have never run a parity check (e.g., full ZFS setups) no longer trigger `json: unsupported value: NaN` errors every 30 seconds ### Changed - **Plugin PLG now includes `project` and `readme` attributes** per [plugin-docs](https://github.com/mstrhakr/plugin-docs) best practices - **CA XML template uses `` root element** instead of legacy `` wrapper per plugin-docs recommendations - **Removed the duplicate template PLG from the release process**: - `unraid-management-agent.plg` is now the only maintained PLG file - Release automation and deploy tooling no longer reference `meta/template/unraid-management-agent.plg` - **Retired redundant root maintenance scripts in favor of Ansible**: - Local deploy, live validation, and diagnostics now flow through `ansible/deploy.yml` - Ansible deploy remains cleanup-focused and does not create pre-deploy backup directories on the Unraid host - **Fixed the Ansible deployment summary counts**: - Prometheus metric families and collector totals now use the same underlying verify data shown by the dedicated verification tasks ### Security - **Log file reads are restricted to the known allowlist**: - Arbitrary filesystem paths are rejected before stat/open, preserving the log API while removing path-traversal findings - **Security annotations were narrowed and documented for trusted system paths and direct argv process execution**: - Raw `gosec`, `golangci-lint`, and the repo security checks now run cleanly with explicit rationale at each trusted boundary - **Upgraded `modelcontextprotocol/go-sdk` from v1.2.0 to v1.3.1** to fix GO-2026-4569 (improper handling of case sensitivity) - **Bumped Go from 1.26.0 to 1.26.1** (latest patch release) ## [2026.03.01] - 2026-03-05 ### Fixed - **`io_utilization_percent` reports cumulative I/O milliseconds, not a utilization percentage** (GitHub Issue #81): - `enrichWithIOStats` previously divided the cumulative `io_ticks` counter from `/sys/block/*/stat` by 10, producing unbounded values (e.g. 29,832,950%) that grew indefinitely with uptime - Now uses a delta-based calculation: tracks previous `io_ticks` per device and computes `(delta_io_ticks / delta_wall_time) * 100`, yielding a proper 0–100% utilization value - On the first collection after startup, the field is omitted (no previous sample to compare against) ## [2026.03.00] - 2026-03-01 ### Fixed - **EventBus goroutine leak** (GitHub Issue #67): - `Unsub()` now closes the channel when it is no longer subscribed to any topic, unblocking `for msg := range ch` goroutines - **CollectorManager broadcastStateChange deadlock** (GitHub Issue #68): - `EnableCollector()` and `DisableCollector()` now snapshot event data under lock, then publish outside the lock to prevent deadlock with subscribers calling `GetStatus`/`GetAllStatus` - **GPUCount inflated by nil entries** (GitHub Issue #70): - `GPUCount` is now incremented inside the nil-guard loop, counting only non-nil GPU entries - **MQTT goroutine leak on reconnect** (GitHub Issue #71): - `handleConnect()` goroutines are now cancelled via context on disconnect/reconnect; discovery, state publishing, and command subscription run sequentially - **EventBus zero-capacity channel** (GitHub Issue #72): - `NewEventBus()` clamps `bufferSize < 1` to `1`, preventing zero-capacity channels that silently drop all messages - **WebSocket subscribe null resets topic filter** (GitHub Issue #73): - `readPump` now uses `json.RawMessage` envelope to distinguish absent `subscribe` key from explicit `null`, correctly resetting topic filter to "all topics" - **GetParityHistoryCache always returns nil** (GitHub Issue #76): - Returns `&dto.ParityCheckHistory{}` empty sentinel instead of `nil` so callers never need nil checks - **Test assertion improvements** (GitHub Issue #78): - Added nil guard to `subscribeMQTTEvents` for nil mqttClient - `TestSubscribeMQTTEvents_NilClient` asserts immediate return instead of relying on timeout - `TestSubscribeMQTTEvents_NotConnected` passes domainCtx instead of nil - EventBus tests updated for channel closure behavior; added `TestEventBus_UnsubPartial` - **VM cannot be resumed/started via API when pmsuspended** (GitHub Issue #65): - Fixed `POST /api/v1/vm/{name}/resume` and `POST /api/v1/vm/{name}/start` failing when VM is in pmsuspended state (Windows sleep) - libvirt DomainResume fails with "domain is pmsuspended"; DomainCreate fails with "domain is already running" - VM controller now detects pmsuspended state and uses `virsh dompmwakeup` to wake the VM, equivalent to pressing Start in Unraid web UI - **Disk size reported at ~50% of actual size** (GitHub Issue #80): - Unraid's `disks.ini` stores `size` in KiB (1024-byte blocks), but the disk collector multiplied by 512 (assuming sectors) - Changed multiplier from 512 to 1024 so `size_bytes` now reports the correct disk capacity - I/O statistics conversions (read/write bytes from `/sys/block/*/stat`) are unchanged as they correctly use 512-byte sectors per Linux kernel convention ### Changed - **Hardcoded topic strings replaced with constants** (GitHub Issue #74): - 11 hardcoded topic name strings in collector debug logs replaced with `constants.Topic*.Name` references - **Network services cache log level** (GitHub Issue #77): - `CacheStore.GetNetworkServicesCache()` failure log changed from `Debug` to `Warning` - **Swagger healthcheck docs updated** (GitHub Issue #79): - `HealthCheckConfig.Type` and `Target` field descriptions now include the `"ping"` probe type - **Nil-deref safety documented** (GitHub Issue #69): - Added comment in alerting engine documenting that `NotificationOverview` and `NotificationCounts` are value types (nil deref not possible) - **Go 1.26.0 Upgrade**: - Upgraded from Go 1.25.0 to Go 1.26.0 for improved performance and latest language features - **Green Tea GC**: 10–40% lower garbage collection overhead (enabled automatically by runtime) - **~30% faster cgo**: Faster native Docker/libvirt operations - **`io.ReadAll` ~2x faster**: Improved `/proc` and `/sys` file reading in all collectors - **Stack-allocated slices**: Fewer heap allocations in hot paths - Modernized 39 Go files via `go fix`: - `interface{}` → `any` across 20 files (98 occurrences) - C-style `for` loops → `for i := range n` across 21 files - `strings.Split` in range → `strings.SplitSeq` (zero-allocation iteration) - `sort.Slice` → `slices.SortFunc` (type-safe sorting) - `t.Context()` in test files (cleaner test contexts) - Refactored `sync.WaitGroup` patterns to use `wg.Go()` (Go 1.26) — eliminates `wg.Add(1); go func() { defer wg.Done()... }` boilerplate in orchestrator and controllers - Refactored `RunMCPStdio()` signal handling to use `signal.NotifyContext` — replaces manual goroutine + channel pattern - Updated CI workflow, devcontainer, and documentation references to Go 1.26 - All dependencies tested and compatible with Go 1.26 ## [2026.02.02] - 2026-02-21 ### Added - **Docker Container Update Management**: - Check individual container for image updates (`GET /docker/{id}/check-update`) - Check all containers for available updates (`GET /docker/updates`) - Get container disk size info (`GET /docker/{id}/size`) - Update individual container to latest image (`POST /docker/{id}/update`, supports `?force=true`) - Bulk update all containers with available updates (`POST /docker/update-all`) - MCP tools: `check_container_updates`, `check_container_update`, `get_container_size`, `update_container`, `update_all_containers` - **Plugin Update Management**: - Check all installed plugins for available updates (`GET /plugins/check-updates`) - Update individual plugin (`POST /plugins/{name}/update`) - Bulk update all plugins (`POST /plugins/update-all`) - MCP tools: `check_plugin_updates`, `update_plugin`, `update_all_plugins` - **VM Snapshots & Cloning**: - Create VM snapshot (`POST /vm/{name}/snapshot`) - List VM snapshots (`GET /vm/{name}/snapshots`) - Delete VM snapshot (`DELETE /vm/{name}/snapshots/{snapshot_name}`) - Restore VM snapshot (`POST /vm/{name}/snapshots/{snapshot_name}/restore`) (GitHub Issue #63) - Clone virtual machine (`POST /vm/{name}/clone?clone_name=...`) - MCP tools: `list_vm_snapshots`, `create_vm_snapshot`, `delete_vm_snapshot`, `restore_vm_snapshot`, `clone_vm` - **Docker Container Logs** (GitHub Issue #64): - Retrieve per-container stdout/stderr logs equivalent to `docker logs` (`GET /docker/{id}/logs`) - Supports `tail`, `since` (RFC3339), and `timestamps` query parameters - MCP tool: `get_container_logs` - **Service Management**: - List all managed services and their status (`GET /services`) - Start/stop/restart system services (`POST /services/{name}/{action}`) - Supported services: docker, libvirt, smb, nfs, ftp, sshd, nginx, syslog, ntpd, avahi, wireguard - MCP tools: `list_services`, `get_service_status`, `service_action` - **Process Listing**: - List running processes sorted by CPU/memory/PID (`GET /processes?sort_by=cpu&limit=50`) - MCP tool: `list_processes` - **New Validation Functions**: - `ValidateContainerRef` — validates Docker container ID or name - `ValidatePluginName` — validates Unraid plugin names - `ValidateServiceName` — validates system service names - `ValidateSnapshotName` — validates VM snapshot names - **16 New MCP Tools** (9 monitoring, 9 control) with proper annotations ## [2026.02.01] - 2026-02-14 ### Added - **CPU Power Consumption Monitoring** (GitHub Issue #60): - Added Intel RAPL (Running Average Power Limit) support via `/sys/class/powercap` - New `cpu_power_watts` and `dram_power_watts` fields in SystemInfo DTO - Real-time CPU package and DRAM power readings in watts - Multi-socket support — power values summed across all CPU packages - Energy counter wraparound handling for long-running systems - Graceful degradation: fields omitted (null) when RAPL is unavailable (AMD, VMs, older hardware) - New Prometheus gauges: `unraid_cpu_power_watts`, `unraid_dram_power_watts` - Automatically exposed via REST API, WebSocket events, and MCP `get_system_info` tool - Comprehensive test suite with 9 tests covering single/multi-socket, wraparound, edge cases - **MCP Streamable HTTP Transport** (GitHub Issue #59): - Implemented MCP 2025-06-18 Streamable HTTP transport specification - `/mcp` endpoint now supports POST, GET, DELETE, and OPTIONS methods - Session management via `Mcp-Session-Id` header - `MCP-Protocol-Version` header validation (supports 2025-06-18 and 2025-03-26) - POST handles both JSON-RPC requests (with response) and notifications (202 Accepted) - GET opens SSE stream for server-initiated messages - DELETE terminates sessions cleanly (404 for unknown sessions per spec) - Full CORS support with proper header exposure - Fixes "No server info found" error in Cursor - Supports Cursor, Claude Desktop, GitHub Copilot, Codex, Windsurf, and Gemini CLI - Comprehensive test suite with race condition detection (25+ tests) - **MCP STDIO Transport**: - New `mcp-stdio` CLI subcommand for local AI client integration - Uses newline-delimited JSON over stdin/stdout (MCP spec 2025-06-18) - Preferred transport for MCP clients running directly on the Unraid server (zero network overhead, no auth needed) - Starts collectors internally for live data — no dependency on running HTTP daemon - STDIO-safe logging: all logs go to file + stderr (stdout reserved for MCP protocol) - Graceful shutdown on SIGTERM/SIGINT with full collector cleanup - Compatible with Claude Desktop, Cursor, and any MCP client that supports STDIO spawning - Hardened `api.Server.Stop()` to handle nil HTTP server in cache-only mode - Unit tests for STDIO transport initialization, error handling, and context cancellation ### Removed - **Legacy SSE MCP Transport**: Removed the deprecated `/mcp/sse` endpoint and old HTTP transport. All clients should use the Streamable HTTP transport at `/mcp` (spec 2025-06-18). --- ## [2026.02.00] - 2026-01-29 ### Fixed - **Disk Model and Serial Number Population** (GitHub Issue #56): - Fixed `serial_number` and `model` fields in `/api/v1/disks` endpoint returning null - Added `enrichWithModelAndSerial()` function to extract model from sysfs and serial from disk ID - Added `parseModelSerialFromID()` fallback parser for when sysfs is unavailable - Disk ID format discovered: `{model}_{serial}` where spaces in model name are replaced with underscores - Examples: `WUH721816ALE6L4_2CGV0URP`, `WDC_WD100EFAX-68LHPN0_JEKV15MZ`, `SPCC_M.2_PCIe_SSD_A240910N4M051200021` - Serial number validation: alphanumeric, 4-30 characters - Model name restoration: underscores converted back to spaces in output - Handles various disk types: WDC drives, Seagate drives, NVMe SSDs, USB drives - Returns null for unassigned/empty disk slots (correct behavior) - Added comprehensive unit tests: `TestParseModelSerialFromID`, `TestEnrichWithModelAndSerialNoDevice`, `TestEnrichWithModelAndSerialEmptyID` - All tests passing, covering edge cases and various disk ID formats --- ## [2026.01.02] - 2026-01-27 ### Changed - **Go 1.25.0 Upgrade**: - Upgraded from Go 1.24.0 to Go 1.25.0 for improved performance and latest language features - Updated development container, documentation, and CI/CD configurations - All dependencies tested and compatible with Go 1.25 - **golangci-lint v2 Migration**: - Upgraded golangci-lint to v2.8.0 (built with Go 1.25.5) - Migrated configuration to v2 format (.golangci.yml) - Moved gofmt and goimports from linters to formatters section per v2 requirements - Pre-commit hooks updated and re-enabled after migration - All code quality checks passing with zero tolerance policy --- ## [2026.01.01] - 2026-01-22 ### Added - **Prometheus Metrics Endpoint** (#54): - New endpoint `GET /metrics` exposes all Unraid data in Prometheus exposition format - Comprehensive metrics covering: System, Array, Disks, Docker, VMs, UPS, Shares, Network Services, GPU - 40+ metrics with proper labels for multi-dimensional querying - Custom Prometheus registry to isolate Unraid metrics from default Go metrics - Enables native Grafana integration via Prometheus data source - Key metrics include: - `unraid_system_info`, `unraid_cpu_usage_percent`, `unraid_memory_*` - `unraid_array_state`, `unraid_array_*_bytes`, `unraid_parity_*` - `unraid_disk_temperature_celsius`, `unraid_disk_status`, `unraid_disk_smart_status` - `unraid_docker_container_state`, `unraid_docker_containers_*` - `unraid_vm_state`, `unraid_vms_*` - `unraid_ups_*`, `unraid_share_*`, `unraid_service_*`, `unraid_gpu_*` - **Network Services Status API**: - New endpoint `GET /api/v1/settings/network-services` returns comprehensive status of all Unraid network services - Monitors 13 network services: SMB, NFS, AFP, FTP, SSH, Telnet, Avahi, NetBIOS, WSD, WireGuard, UPnP, NTP, Syslog - Each service includes: `name`, `enabled`, `running`, `port`, `description` - Summary counts: `total_services`, `enabled_services`, `running_services` - Parses configuration from `var.ini`, `ident.cfg`, and `tips.and.tweaks.cfg` - Runtime status via process detection in `/proc` - Useful for monitoring dashboards and home automation integrations - **Global Disk Temperature Thresholds API** (#45): - New endpoint `GET /api/v1/settings/disk-thresholds` returns system-wide temperature warning/critical thresholds - Includes separate thresholds for HDDs (hot/max) and SSDs (hotssd/maxssd) from `dynamix.cfg` - Useful for monitoring integrations (Grafana, Home Assistant) that need threshold context - **Per-Disk Temperature Threshold Overrides** (#46): - Extended `DiskInfo` DTO with `temp_warning` and `temp_critical` fields - Parses per-disk overrides from individual disk `.cfg` files - Returns `null` when using global defaults, integer when overridden - **Parity Check Schedule API** (#47): - New endpoint `GET /api/v1/array/parity-check/schedule` returns complete parity schedule configuration - Includes: enabled status, frequency (daily/weekly/monthly/custom), scheduled day/time - Parses from `/boot/config/plugins/dynamix/parity-checks.cron` - **Mover Schedule & Status API** (#48): - New endpoint `GET /api/v1/settings/mover` returns mover configuration and status - Includes: schedule (cron-style), enabled status, whether currently running - Source/destination thresholds, action on share fill, and current operation status - **Docker & VM Service Status API** (#49): - New endpoint `GET /api/v1/settings/services` returns enabled/disabled status - `docker_enabled`: whether Docker service is enabled in Unraid settings - `vm_enabled`: whether VM Manager (libvirt) is enabled - **OS Update Availability API** (#50): - New endpoint `GET /api/v1/updates` returns Unraid OS and plugin update status - Includes: `os_update_available`, `current_version`, `available_version` - Plugin update counts: `plugin_updates_count`, `plugins_with_updates` array - **USB Flash Drive Health API** (#51): - New endpoint `GET /api/v1/system/flash` returns flash boot drive health - Includes: `device`, `total_bytes`, `used_bytes`, `free_bytes`, `used_percent` - `mount_point` and `filesystem` type for diagnostics - **Installed Plugins List API** (#52): - New endpoint `GET /api/v1/plugins` returns all installed plugins with metadata - Each plugin includes: `name`, `version`, `author`, `icon`, `support_url` - `update_available` and `update_version` fields for upgrade awareness - **Share Cache Pool Settings** (#53): - Extended `ShareInfo` DTO with cache pool configuration fields - `cache_pool`: primary cache pool name (or empty for "no") - `cache_pool2`: secondary cache pool for prefer destinations - `mover_action`: computed action ("no_cache", "cache_only", "cache_to_array", "array_to_cache", "cache_prefer") - **Pre-commit Hooks for Code Quality**: - Comprehensive pre-commit configuration with zero tolerance for linting warnings and errors - Automatic code formatting (gofmt, goimports) - Static analysis (go vet, golangci-lint) - Security scanning (gosec, govulncheck) - Secret detection (detect-secrets) - Unit test execution with race detection - Custom checks: VERSION format validation, CHANGELOG reminder, debug print detection - GitHub Actions workflow for CI enforcement - Setup script: `scripts/setup-pre-commit.sh` - Makefile targets: `pre-commit-install`, `pre-commit-run`, `lint`, `security-check` - **Model Context Protocol (MCP) Support** - AI Agent Integration: - New `/mcp` endpoint enables AI agents (Claude, GPT, etc.) to interact with Unraid - Full MCP protocol implementation using mcp-golang v0.16.0 - **18 Monitoring Tools**: system info, array status, disk health, containers, VMs, UPS, GPU, network, notifications, ZFS - **7 Control Tools** (with confirmation for destructive actions): container/VM actions, array control, parity check, reboot/shutdown - **5 MCP Resources** for real-time data access - **3 MCP Prompts** for guided AI interactions - **OpenAPI/Swagger Documentation**: - Interactive API documentation available at `/swagger/` - Full OpenAPI 2.0 specification with 76+ documented endpoints - Auto-generated from code annotations using swaggo/swag ### Fixed - **Parity Check History Null Records (Issue #44)**: - Fixed parity history endpoint returning `null` for records when parity check history exists - Rewrote `parseLine()` to correctly parse the actual Unraid parity log format - Fixed JSON marshaling issue: empty records now return `[]` instead of `null` - Added multi-format support for backward compatibility with older Unraid versions: - 5-field legacy format (pre-2022 Unraid versions) - 7-field current format (2022-present) - 10-field extended format (with parity check tuning plugin) - Added `parseSpeed()` helper to handle both numeric bytes/sec and human-readable "XX.X MB/s" formats - Added comprehensive tests for all parity log format variations - **Log File Accumulation** - Fixed issue where log files were accumulating and consuming excessive disk space (80MB+): - Changed lumberjack `MaxBackups` from 0 to 1 (0 means "keep all", not "keep none") - Changed lumberjack `MaxAge` from 0 to 1 day - Added `cleanupOldLogs()` function that runs on startup to remove old `.gz` backup files - Log rotation now properly limits to 1 backup file maximum - **Dark Theme Support** - Fixed plugin UI not respecting Unraid's dark theme: - Replaced hardcoded CSS colors with Unraid CSS variables (`var(--line-color)`, `var(--text-secondary)`, `var(--input-background)`, etc.) - Status badges, tables, and form elements now properly adapt to light/dark themes - Uses `color: inherit` for text to match theme colors - **Parity Check Status Detection (Issue #41)**: - Fixed parity check status not detecting "paused" state - now correctly parses `mdResyncDt` field (0 = paused, >0 = running) - Fixed parity check progress percentage showing 0 - now calculates from `mdResyncPos / mdResyncSize * 100` - Added support for detecting clearing and reconstructing operations via `sbSyncAction` field - Added debug logging for parity check operations to aid troubleshooting - Status values: `""` (idle), `"paused"`, `"running"`, `"clearing"`, `"reconstructing"` - **Disk Temperature Reporting**: - Improved handling of temperature value `"*"` which indicates spun-down disk - Temperature 0 is now documented expected behavior for standby disks (to avoid spinning up disks for temperature checks) - Enhanced debug logging for temperature parsing - **Power Consumption Optimization** (Issue #8): - Increased default collection intervals to reduce CPU wakeups - System: 5s → 15s, Array: 10s → 30s, Docker: 10s → 30s - VM: 10s → 30s, UPS: 10s → 60s, GPU: 10s → 60s - Optimized Docker stats to batch all containers in single command - Reduced intel_gpu_top timeout (5s → 2s) and samples (2 → 1) - Expected power savings: 15-20W on affected systems ### Changed - **Default Log Level** - Changed default log level from "warning" to "info" for better visibility - **Removed Log Level UI Option** - Log level is now set via CLI only (`--log-level` flag), removed from plugin settings page - **Industry-Standard Default Intervals**: - Updated defaults to follow industry monitoring standards (Zabbix, Prometheus, Datadog) - Disk Health: 30s → 300s (5 min) - SMART data rarely changes - ZFS Pools: 30s → 300s (5 min) - pool health rarely changes - Array Status: 30s → 60s - array state changes infrequently - Hardware Info: 300s → 600s (10 min) - static hardware info - VM Monitoring: 30s → 60s - VMs typically have stable state - Network: 30s → 60s - interface config rarely changes - Registration: 300s → 600s (10 min) - license info is static - **Start Script**: Fixed environment variable passing to Go binary through sudo - **Low Power Mode** (`--low-power-mode` or `UNRAID_LOW_POWER=true`): - New option for resource-constrained/older hardware (e.g., HP N40L with AMD Turion) - Multiplies all collection intervals by 4x when enabled - Reduces CPU wake-ups and allows deeper C-states - Ideal for users experiencing high CPU load from the plugin - **Runtime Collector Management API** (#35): - New endpoint `POST /api/v1/collectors/{name}/enable` - Enable a collector at runtime - New endpoint `POST /api/v1/collectors/{name}/disable` - Disable a collector at runtime - New endpoint `PATCH /api/v1/collectors/{name}/interval` - Update collection interval - New endpoint `GET /api/v1/collectors/{name}` - Get status of a single collector - Enable/disable collectors without restarting the agent - Dynamic interval updates (5-3600 seconds) - System collector protected from being disabled (always required) - WebSocket broadcast for collector state changes (`collector_state_change` event) - Full idempotent operations (enable already enabled = no-op) - Enhanced `/api/v1/collectors/status` with real-time data: - `last_run` timestamp for each collector - `error_count` tracking - `required` flag to indicate if collector can be disabled - **Network Access URLs Endpoint** (`GET /api/v1/network/access-urls`) (#19): - New endpoint to get all methods to access the Unraid server - Returns LAN IPv4 addresses from all network interfaces - Includes mDNS hostname (hostname.local) for Bonjour/Avahi discovery - Detects and includes WireGuard VPN interface addresses - Retrieves public WAN IP (if accessible) - Lists global IPv6 addresses for dual-stack access - URL types: `lan`, `mdns`, `wireguard`, `wan`, `ipv6`, `other` - Useful for dashboards, mobile apps, and connection discovery ### Performance - **Docker SDK Collector (NEW)** - Massive performance improvement: - Replaced CLI-based Docker collector with Docker SDK (socket API) - Uses `/var/run/docker.sock` directly instead of spawning `docker` processes - **~530x faster**: Container list from 5.8s → 10-15ms - **Total docker collection: 15-43ms** (was 8.8 seconds!) - Eliminates process spawning overhead on resource-constrained systems - Still reads memory stats from cgroup v2 filesystem for optimal performance - **VM Libvirt Collector (NEW)** - Native libvirt API integration: - Replaced CLI-based VM collector with libvirt Go bindings - Uses direct RPC to libvirt daemon instead of `virsh` commands - **~100-200x faster**: VM list from 1-2s → 6-7ms - Collects CPU, memory, disk I/O, and network I/O stats efficiently - Graceful fallback if libvirt is not available - **Docker Collector Optimizations** (Community feedback: HP N40L performance issue): - **Batched docker inspect calls**: Reduced from N separate calls to 1 batched call (3.5x faster) - **Skip inspect for stopped containers**: Only running containers get detailed inspection - Overall docker collection cycle reduced from ~8.8s to ~5.9s on test server - Significantly reduces CPU spikes on older hardware - **NUT (Network UPS Tools) Support** (`GET /api/v1/nut`): - Full support for the [NUT-unRAID plugin](https://github.com/desertwitch/NUT-unRAID) - New dedicated `/api/v1/nut` endpoint with comprehensive UPS data - Detects NUT plugin installation and running state - Returns detailed configuration from nut-dw.cfg - Lists all configured UPS devices - Provides detailed UPS status including: - Battery charge, voltage, runtime, type, status - Input/output voltage, frequency, current - Load percentage and real/apparent power - Device identification (manufacturer, model, serial) - Driver information and raw variables - Human-readable status text conversion (OL → "Online", OB → "On Battery", etc.) - WebSocket broadcast support for real-time NUT updates - **Improved UPS Collector NUT Detection**: - Fixed `upsc` command to properly use `@localhost` suffix - UPS endpoint (`/api/v1/ups`) now correctly falls back to NUT when apcupsd unavailable - Both `/api/v1/ups` (basic) and `/api/v1/nut` (detailed) work simultaneously - **Collectors Status API Endpoint** (`GET /api/v1/collectors/status`): - New endpoint to view status of all 14 collectors - Shows enabled/disabled state for each collector - Shows configured interval (in seconds) for each collector - Shows running status ("running" or "disabled") - Provides summary counts: total, enabled_count, disabled_count - Useful for monitoring and debugging collector configuration - **Disable Collectors Feature**: - Added "Disabled" option to all collection interval dropdowns - Setting interval to 0 completely stops the collector (no CPU/memory usage) - Useful for disabling collectors for hardware you don't have (GPU, UPS, ZFS) - Disabled collectors shown with red border highlight in UI - Backend logs which collectors are disabled at startup - **Environment Variable to Disable Collectors** (`UNRAID_DISABLE_COLLECTORS`): - New environment variable for disabling collectors without UI - Comma-separated list of collector names (e.g., `UNRAID_DISABLE_COLLECTORS='gpu,ups,zfs'`) - Validates collector names and warns about unknown names - System collector cannot be disabled (always required) - Ideal for Docker deployments or automated setups - **CLI Flag to Disable Collectors** (`--disable-collectors`): - New command-line flag for disabling collectors at startup - Usage: `--disable-collectors=gpu,ups,zfs` - Same validation as environment variable - Both env var and CLI flag work (CLI flag populates from env var) - **Extended Collection Intervals (up to 24 hours)**: - All collectors now support intervals from 5 seconds to 24 hours (86400 seconds) - New interval options: 1 hour, 2 hours, 4 hours, 6 hours, 12 hours, 24 hours - Ideal for static data that rarely changes (hardware info, registration/license) - Reduces power consumption significantly for infrequently-changing data - **Web UI for Collection Intervals** (Issue #8): - New settings page accessible from Unraid Settings → Unraid Management Agent - Dropdown menus with predefined interval options (5 seconds to 30 minutes) - Organized into logical sections: System Monitoring, Containers & VMs, Hardware, Storage, Other - Human-readable labels (e.g., "30 seconds", "1 minute", "5 minutes") - Power consumption warning explaining impact of faster intervals - Automatic service restart when clicking Apply - No need to manually edit config files - **Configurable Collection Intervals**: - All 14 collection intervals now configurable via UI or config file - Environment variables properly passed to Go binary on service start - Config file persists settings across reboots at `/boot/config/plugins/unraid-management-agent/config.cfg` ### Removed --- ## [2025.12.0] - 2025-12-18 ### Fixed - **Array Disk Counts Inverted** (Issue #30): - Fixed `num_data_disks` and `num_parity_disks` being swapped in `/api/v1/array` endpoint - Removed incorrect use of `mdNumDisabled` field (disabled disks) for data disk count - Data disks now correctly calculated as: `mdNumDisks - active_parity_count` - Parity disk count now only includes active parity disks (excludes disabled/missing) - Disabled parity disks (DISK_NP_DSBL, DISK_NP, DISK_DSBL) are no longer counted - Affects Home Assistant integration and other API clients relying on accurate disk counts --- ## [2025.11.26] - 2025-11-28 ### Added - **Enhanced Log API** (Issue #28): - Expanded `commonLogPaths` from 4 to 30+ common Unraid log file paths - New log files include: dmesg, messages, cron, debug, btmp, lastlog, wtmp, graphql-api.log, unraid-api.log, recycle.log, dhcplog, mover.log, apcupsd.events, nohup.out, nginx error/access logs, vfsd.log, smbd.log, nfsd.log, samba logs, and more - New REST endpoint: `GET /api/v1/logs/{filename}` for direct log file access by filename - Added `lines_returned` field to `LogFileContent` DTO for pagination clarity - Added `ValidateLogFilename()` function in `daemon/lib/validation.go` for secure filename validation - Proper path traversal protection (CWE-22) on new `/logs/{filename}` endpoint - Matches Unraid GraphQL API log coverage for parity - **System Reboot and Shutdown API** (Issue #20): - New REST endpoint: `POST /api/v1/system/reboot` to initiate server reboot - New REST endpoint: `POST /api/v1/system/shutdown` to initiate server shutdown - New `SystemController` in `daemon/services/controllers/system.go` - Enables Home Assistant integration for server power management - Graceful shutdown/reboot using standard Linux shutdown command --- ## [2025.11.25] - 2025-11-18 ### Security - **CRITICAL: Fixed 5 CWE-22 Path Traversal Vulnerabilities** (High Severity): - Fixed path traversal vulnerability in notification controller (`daemon/services/controllers/notification.go`) - Added `validateNotificationID()` function to validate notification IDs before file operations - Protected functions: `ArchiveNotification()`, `UnarchiveNotification()`, `DeleteNotification()` - Blocks parent directory references (`..`), absolute paths, and path separators - Enforces `.notify` file extension requirement - Fixed path traversal vulnerabilities in config collector (`daemon/services/collectors/config.go`) - Added `validateShareName()` function to validate share names before config file access - Protected functions: `GetShareConfig()`, `UpdateShareConfig()` - Blocks parent directory references (`..`), absolute paths, and path separators - Enforces 255 character limit for share names - Implemented defense-in-depth validation strategy: - Input validation at function level (not just API level) - Path normalization using `filepath.Clean()` - Path containment verification using `strings.HasPrefix()` - Multiple validation layers for comprehensive protection - Added comprehensive security test coverage: - `daemon/services/controllers/notification_security_test.go` (4 test suites, 27 test cases) - `daemon/services/collectors/config_security_test.go` (3 test suites, 21 test cases) - All tests validate rejection of malicious path traversal attempts - **Impact**: Prevents attackers from reading or writing arbitrary files on the system - **CWE-22**: Improper Limitation of a Pathname to a Restricted Directory - **Affected Versions**: All versions prior to v2025.11.25 - **Recommendation**: All users should upgrade immediately to v2025.11.25 ### Documentation - Added `SECURITY_FIX_PATH_TRAVERSAL.md` with detailed vulnerability analysis and fix documentation --- ## [2025.11.24] - 2025-11-18 ### Added - **ZFS Storage Pool Support** (GitHub Issue #9): - Complete ZFS integration for monitoring ZFS storage pools on Unraid - New REST API endpoints: - `GET /api/v1/zfs/pools` - List all ZFS pools with comprehensive metrics - `GET /api/v1/zfs/pools/{name}` - Get detailed information about a specific pool - `GET /api/v1/zfs/datasets` - List all ZFS datasets (filesystems and volumes) - `GET /api/v1/zfs/snapshots` - List all ZFS snapshots - `GET /api/v1/zfs/arc` - Get ZFS ARC (Adaptive Replacement Cache) statistics - ZFS collector with 30-second collection interval - Real-time WebSocket events for ZFS pool, dataset, snapshot, and ARC stats updates - Comprehensive ZFS data structures: - Pool metrics: size, allocated space, free space, fragmentation, capacity, dedup ratio, compression ratio - Pool health: state, health status, read/write/checksum errors - VDEVs: virtual device information (mirrors, raidz, disks) - Datasets: filesystem and volume information with compression and quota details - Snapshots: point-in-time snapshots with creation time and space usage - ARC statistics: cache hit ratios, size metrics, L2ARC stats - Automatic detection of ZFS availability (gracefully handles systems without ZFS) - Full support for ZFS pool properties: autoexpand, autotrim, readonly, altroot - Scrub/resilver status tracking ### Technical Details - **New Files**: - `daemon/dto/zfs.go`: ZFS data transfer objects (ZFSPool, ZFSVdev, ZFSDevice, ZFSDataset, ZFSSnapshot, ZFSARCStats, ZFSIOStats) - `daemon/services/collectors/zfs.go`: ZFS collector implementation with parsers for zpool/zfs command output - `docs/ZFS_INVESTIGATION_FINDINGS.md`: Complete investigation findings and implementation documentation - **Modified Files**: - `daemon/constants/const.go`: Added ZFS binary paths and collection interval constants - `daemon/services/orchestrator.go`: Integrated ZFS collector into application lifecycle - `daemon/services/api/server.go`: Added ZFS cache fields and event subscriptions - `daemon/services/api/handlers.go`: Implemented ZFS endpoint handlers - **ZFS Data Sources**: - `/usr/sbin/zpool list -Hp`: Pool metrics (parseable format) - `/usr/sbin/zpool status -v`: Pool status, vdev tree, error counters - `/usr/sbin/zpool get all`: Pool properties - `/usr/sbin/zfs list -Hp`: Dataset information - `/proc/spl/kstat/zfs/arcstats`: ARC cache statistics - **Testing**: - Validated on Unraid 7.2.0 with ZFS 2.3.4-1 - Tested with "garbage" pool (222GB, single disk, ONLINE) - All endpoints returning correct data - ARC hit ratio: 99.89% (53,172 hits, 58 misses) --- ## [2025.11.23] - 2025-11-17 ### Changed - **Dependency Updates**: - Updated `github.com/alecthomas/kong` from v0.9.0 to v1.13.0 - Updated `golang.org/x/sys` from v0.13.0 to v0.38.0 - Upgraded Go language version from 1.23 to 1.24.0 (required by golang.org/x/sys v0.38.0) - Migrated from deprecated `github.com/vaughan0/go-ini` (last updated 2013) to actively maintained `gopkg.in/ini.v1` v1.67.0 - All dependencies now use modern, actively maintained libraries - **Code Quality Improvements**: - Fixed all pre-existing linting issues (now 0 errors, 0 warnings) - Converted if-else chains to switch statements for better readability (gocritic) - Reduced cyclomatic complexity by extracting helper functions (gocyclo) - Renamed `daemon/common` package to `daemon/constants` for better clarity (revive) - Improved code maintainability and adherence to Go best practices ### Technical Details - **Linting Fixes**: - `daemon/lib/dmidecode.go`: Converted cache level parsing to switch statement - `daemon/lib/ethtool.go`: Extracted `parseEthtoolKeyValue()` helper (complexity 34 → 18) - `daemon/services/collectors/disk.go`: Extracted `parseDisksINI()`, `parseDiskKeyValue()`, `enrichDisks()` helpers (complexity 32 → 12) - `daemon/services/collectors/disk.go`: Converted disk role determination to switch statement - `daemon/services/collectors/gpu.go`: Converted Intel GPU error handling to switch statement - **INI Library Migration**: - Updated `daemon/lib/parser.go`: Refactored `ParseINIFile()` to use new API - Updated `daemon/services/collectors/array.go`: Migrated `collectArrayStatus()` and `countParityDisks()` - Updated `daemon/services/collectors/registration.go`: Migrated `collectRegistration()` - API changes: `ini.LoadFile()` → `ini.Load()`, `file.Get()` → `section.HasKey()` + `section.Key().String()` All tests pass successfully. Builds verified for local and release targets. --- ## [2025.11.22] - 2025-11-17 ### Added - **Management Agent Version Field** (Issue #26): - Added `agent_version` field to `/api/v1/system` endpoint - Returns the Unraid Management Agent plugin version (e.g., "2025.11.22") - Complements the `version` field which returns the Unraid OS version - Enables API clients to: - Implement agent-specific compatibility checks - Detect features based on agent capabilities - Display complete version information in diagnostics - Track agent updates independently from OS updates - Improves Home Assistant integration and other API clients - Version is automatically populated from the VERSION file during build --- ## [2025.11.21] - 2025-11-17 ### Fixed - **Motherboard Temperature API** (Issue #24): - Fixed motherboard temperature returning 0 instead of actual value - Improved sensor parser to capture sensor labels (e.g., "MB Temp") from `sensors -u` output - Updated temperature matching logic to correctly identify motherboard temperature sensor - Motherboard temperature now displays actual readings (e.g., 45°C) instead of 0°C - Affects Home Assistant integration and other API clients relying on temperature data - **Unraid OS Version Field** (Issue #25): - Fixed system API returning empty string for `version` field - Added `getUnraidVersion()` function to read Unraid OS version from `/etc/unraid-version` - Fallback to `/var/local/emhttp/var.ini` if primary version file is unavailable - Version field now correctly displays Unraid OS version (e.g., "7.2.0") - Improves device information display in Home Assistant and other integrations - Enables version-specific feature detection and compatibility checks --- ## [2025.11.20] - 2025-11-16 ### Fixed - **VM Endpoint ID Field** (Issue #22): - Fixed VM endpoint returning empty string for `id` field - Changed from using `virsh domid` (runtime ID) to `virsh domuuid` (persistent UUID) - VM IDs are now stable, unique identifiers that work for all VM states (running, shut off, paused) - Provides consistent identification for API clients and automation systems - Falls back to VM name if UUID is not available - **Array Parity Status** (Issue #21): - Fixed incorrect parity status reporting (`parity_valid: false` when parity is actually valid) - Fixed incorrect parity disk count (`num_parity_disks: 0` when parity disks exist) - Now correctly counts parity disks from `disks.ini` (field `type="Parity"`) - Improved parity validity logic to check `sbSynced` timestamp and `sbSyncErrs` count - Parity is marked valid only when: - At least one parity disk exists - Parity has been synced (sbSynced is non-zero timestamp) - No parity errors exist (sbSyncErrs is 0) ### Technical Details - **VM Collector**: Updated `getVMID()` to use `virsh domuuid` for stable UUID-based identification - **Array Collector**: Added `countParityDisks()` function to parse disks.ini and count parity disks - **Parity Logic**: Improved validation to check timestamp-based sync status instead of yes/no flag - **Compatibility**: Both fixes maintain backward compatibility with existing API clients --- ## [2025.11.19] - 2025-11-16 ### Fixed - **Code Quality Improvements**: - Resolved all critical linting warnings across the entire codebase (93% reduction from 302 to 22 warnings) - Added comprehensive godoc comments to all exported types, functions, and methods - Fixed all gosec security warnings with proper justifications - Fixed revive warnings (indent-error-flow, empty-block) - Fixed staticcheck warnings (unnecessary nil checks) - Fixed unconvert warnings (unnecessary type conversions) - Fixed ineffassign warnings (ineffectual assignments) - Achieved zero tolerance policy compliance for all critical linting issues ### Technical Details - **Files Modified**: 21 files across collectors, controllers, API server, and main package - **Linting Compliance**: Zero critical errors, zero critical warnings - **Documentation**: All exported symbols now have proper godoc comments - **Security**: All gosec warnings properly justified with #nosec comments - **Code Style**: Removed unnecessary else blocks, nil checks, and type conversions --- ## [2025.11.18] - 2025-11-16 ### Added - **Unassigned Devices Plugin Support** (Issue #7): - Complete support for Unassigned Devices plugin integration - New `/api/v1/unassigned` endpoint to list all unassigned devices and remote shares - New `/api/v1/unassigned/devices` endpoint for unassigned devices only - New `/api/v1/unassigned/remote-shares` endpoint for remote shares only - Automatic detection of unassigned disk devices (USB drives, eSATA, internal disks not in array) - Support for remote SMB/NFS shares and ISO file mounts - Per-device information: serial number, model, partitions, mount status, spin state - Per-partition information: label, filesystem, mount point, size, usage, SMB/NFS share status - Automatic filtering of array disks, loop devices, md devices, and zram - Real-time monitoring with 30-second collection interval - WebSocket real-time updates when unassigned devices change ### Technical Details - **New DTOs**: `daemon/dto/unassigned.go` - UnassignedDevice, UnassignedPartition, UnassignedRemoteShare, UnassignedDeviceList structures - **New Collector**: `daemon/services/collectors/unassigned.go` - Unassigned devices collector with lsblk integration - **Device Discovery**: Uses lsblk to enumerate all block devices and filters out array disks - **Array Disk Detection**: Parses `/var/local/emhttp/disks.ini` to identify array disks - **Partition Information**: Collects filesystem type, mount point, size, usage for each partition - **Remote Share Support**: Detects mounted ISO files from `/proc/mounts` - **API Integration**: Added 3 new monitoring endpoints for unassigned devices - **WebSocket Events**: Real-time `unassigned_devices_update` events for connected clients - **Collection Interval**: 30 seconds for device discovery and status updates --- ## [2025.11.17] - 2025-11-16 ### Added - **Unraid Notifications System** (Issue #10): - Complete notification management system with file monitoring - New `/api/v1/notifications` endpoint to list all notifications with overview counts - New `/api/v1/notifications/unread` endpoint for unread notifications only - New `/api/v1/notifications/archive` endpoint for archived notifications only - New `/api/v1/notifications/overview` endpoint for notification counts by importance - New `/api/v1/notifications/{id}` endpoint to get specific notification - Create custom notifications via POST `/api/v1/notifications` - Archive notifications via POST `/api/v1/notifications/{id}/archive` - Unarchive notifications via POST `/api/v1/notifications/{id}/unarchive` - Delete notifications via DELETE `/api/v1/notifications/{id}` - Archive all unread notifications via POST `/api/v1/notifications/archive/all` - Real-time file monitoring with fsnotify for instant notification updates - Automatic notification discovery from `/usr/local/emhttp/state/notifications/` - Support for all importance levels: alert, warning, info - Notification counts by type (unread/archive) and importance level - WebSocket real-time updates when notifications change - Filter notifications by importance level via query parameter ### Technical Details - **New DTOs**: `daemon/dto/notification.go` - Notification, NotificationOverview, NotificationCounts, NotificationList structures - **New Collector**: `daemon/services/collectors/notification.go` - File-based notification collector with fsnotify monitoring - **New Controller**: `daemon/services/controllers/notification.go` - Notification CRUD operations (create, archive, unarchive, delete) - **File Monitoring**: Uses fsnotify to watch notification directories for real-time updates - **Collection Interval**: 15 seconds (with instant updates via file watcher) - **Notification Format**: Parses Unraid .notify files with key-value pairs - **Archive Support**: Moves notifications between active and archive directories - **Bulk Operations**: Archive all unread notifications at once - **Security**: Proper file permissions (0644 for files, 0755 for directories) - **Error Handling**: Graceful handling of missing directories, parse errors, and file operations --- ## [2025.11.16] - 2025-11-16 ### Added - **System Log File Access API** (Issue #15): - New `/api/v1/logs` endpoint to list all available log files - Log content retrieval with pagination support via query parameters - Tail behavior: `?path=/var/log/syslog&lines=100` returns last 100 lines - Range retrieval: `?path=/var/log/syslog&start=500&lines=100` returns lines 500-600 - Automatic discovery of common Unraid log files (syslog, docker.log, libvirtd.log, agent log) - Plugin log file discovery from `/boot/config/plugins/*/logs/*.log` - Directory traversal protection for secure log file access - Returns log metadata: name, path, size, modified timestamp - Returns log content: full content string, line array, total lines, start/end line numbers ### Technical Details - **New DTOs**: `daemon/dto/logs.go` - LogFile and LogFileContent structures - **New API Module**: `daemon/services/api/logs.go` - Log listing and content retrieval logic - **Security**: Path validation prevents directory traversal attacks - **Pagination**: Supports both tail behavior (last N lines) and range retrieval (start + lines) - **Common Logs**: Automatically discovers syslog, Docker, libvirt, and plugin logs - **Error Handling**: Graceful handling of missing files, permission errors, and invalid paths --- ## [2025.11.15] - 2025-11-16 ### Added - **Registration/License Information API** (Issue #18): - New `/api/v1/registration` endpoint to retrieve Unraid license and registration information - Returns license type (trial, basic, plus, pro, lifetime), state (valid, expired, invalid, trial), expiration dates, server name, and registration GUID - Automatically determines license state based on expiration date and license type - Supports all Unraid license types including trial, basic, plus, pro, and lifetime/unleashed licenses - Real-time updates via WebSocket when registration information changes - Graceful handling of missing or invalid registration data ### Technical Details - **New DTO**: `daemon/dto/registration.go` - Registration data structure with license type, state, expiration, server name, and GUID - **New Collector**: `daemon/services/collectors/registration.go` - Parses `/var/local/emhttp/var.ini` for registration data - **API Integration**: Added registration cache, handler, route, and event subscription to API server - **Event Bus**: Registration collector publishes `registration_update` events to the PubSub hub - **Collection Interval**: Uses hardware collection interval (60 seconds) for registration data updates --- ## [2025.11.14] - 2025-11-16 ### Added - **Enhanced GPU Monitoring and Multi-GPU Support** (Issue #8 - High Priority Items): - **GPU Identification Fields**: Added `Index`, `PCIID`, `Vendor`, and `UUID` fields to GPUMetrics DTO - `Index`: GPU index for multi-GPU systems (0-based) - `PCIID`: PCI bus ID (e.g., "0000:01:00.0") - `Vendor`: GPU vendor ("nvidia", "intel", "amd") - `UUID`: Device UUID (NVIDIA only) - **Fan Speed Monitoring**: - NVIDIA: `FanSpeed` field (percentage, 0-100) - AMD: `FanRPM` and `FanMaxRPM` fields (discrete GPUs only) - **AMD GPU Detection Improvements**: - Switched from `rocm-smi` to `radeontop` for broader AMD GPU compatibility - Now supports consumer Radeon GPUs (RX 5000/6000/7000 series) - Fallback to `rocm-smi` for datacenter GPUs (Instinct series) - AMD GPU temperature and fan speed read from sysfs - **Multiple Intel GPU Detection**: - Fixed bug where only first Intel GPU was detected - Now detects all Intel GPUs (iGPU + discrete Arc GPUs) - Each Intel GPU gets unique index and PCI ID - **NVIDIA Enhancements**: - Added UUID field for unique GPU identification - Added fan speed monitoring (percentage) - Added PCI bus ID extraction ### Changed - **GPU Collector**: Complete refactor to support multi-GPU systems - Intel GPU collector now detects ALL Intel GPUs (removed `break` statement) - AMD GPU collector uses `radeontop` by default, `rocm-smi` as fallback - NVIDIA GPU collector queries additional fields (UUID, PCI ID, fan speed) - All GPU collectors now populate vendor, index, and PCI ID fields - **GPU API Response**: Already returns array of GPUMetrics (no breaking change) - **GPU Detection Order**: Intel → NVIDIA → AMD (unchanged) ### Technical Details - **Intel Multi-GPU**: Collects metrics for each detected Intel GPU separately - Note: `intel_gpu_top` limitation - reports only first GPU's metrics - Multiple GPUs detected via lspci, but metrics may be from primary GPU - **AMD radeontop**: Parses dump mode output for GPU/VRAM utilization - Temperature read from `/sys/class/drm/card*/device/hwmon/hwmon*/temp1_input` - Fan speed read from `/sys/class/drm/card*/device/hwmon/hwmon*/fan1_input` - Fan max RPM read from `/sys/class/drm/card*/device/hwmon/hwmon*/fan1_max` - **NVIDIA nvidia-smi**: Extended query to include `pci.bus_id`, `uuid`, `fan.speed` - **Driver Versions**: Extracted from `modinfo` (Intel/AMD) or `nvidia-smi` (NVIDIA) ### Compatibility - **Backward Compatible**: All new fields use `omitempty` JSON tags - **AMD GPU Requirements**: - Consumer GPUs: Requires `radeontop` (install via Nerd Tools or manual) - Datacenter GPUs: Requires `rocm-smi` (ROCm packages) - **Intel GPU Requirements**: `intel_gpu_top` from `igt-gpu-tools` (Unraid 6.12+) - **NVIDIA GPU Requirements**: `nvidia-smi` (included with NVIDIA driver) ### Known Limitations - **Intel Multi-GPU**: `intel_gpu_top` doesn't support per-device metrics - Multiple Intel GPUs detected, but metrics may be from primary GPU only - This is a limitation of `intel_gpu_top` tool, not the agent - **AMD Fan Speed**: Only available on discrete GPUs with fan sensors - Integrated AMD GPUs (APUs) don't have fan sensors - **AMD radeontop**: May require manual installation on some systems --- ## [2025.11.13] - 2025-11-16 ### Added - **Enhanced Share List API** (Issue #6): Eliminated N+1 query problem for share information - Share list endpoint `/api/v1/shares` now includes configuration details directly - New fields in ShareInfo DTO: - `comment` - Share comment/description from config - `smb_export` - Boolean indicating if share is exported via SMB - `nfs_export` - Boolean indicating if share is exported via NFS - `storage` - Storage location: "cache", "array", "cache+array", or "unknown" - `use_cache` - Cache usage setting: "yes", "no", "only", "prefer" - `security` - Security setting: "public", "private", "secure" - Share collector automatically enriches shares with config data - SMB export detection based on security settings and export field - NFS export detection based on export field - Storage location determined from UseCache setting - All new fields use `omitempty` for backward compatibility - Single API call now provides complete share information matching Unraid UI - Graceful error handling: shares without config files return basic info only ### Changed - Share collector now reads individual share config files during collection - Share enrichment happens automatically for all shares in the list ### Performance - Share collection remains at 60-second interval - Config file reads are lightweight and fast - No performance impact from enrichment process --- ## [2025.11.12] - 2025-11-16 ### Added - **Hardware Information API** (Issue #5): Comprehensive hardware details via dmidecode and ethtool - New `/api/v1/hardware/*` endpoints exposing detailed hardware information - `/api/v1/hardware/full` - Complete hardware information - `/api/v1/hardware/bios` - BIOS information (vendor, version, release date, characteristics) - `/api/v1/hardware/baseboard` - Motherboard information (manufacturer, product name, version, serial number) - `/api/v1/hardware/cpu` - CPU hardware details (socket, manufacturer, family, max speed, core/thread count, voltage) - `/api/v1/hardware/cache` - CPU cache information (L1/L2/L3 cache levels, size, type, associativity) - `/api/v1/hardware/memory-array` - Memory array information (location, max capacity, error correction, number of devices) - `/api/v1/hardware/memory-devices` - Individual memory module details (size, speed, manufacturer, part number, type) - Hardware collector runs every 5 minutes (hardware information is static) - All hardware data is cached and broadcast via WebSocket for real-time updates - **Enhanced System Information**: - `HVMEnabled` - Hardware virtualization support (Intel VT-x/AMD-V detection via /proc/cpuinfo) - `IOMMUEnabled` - IOMMU support detection (kernel command line and /sys/class/iommu/) - `OpenSSLVersion` - OpenSSL version information - `KernelVersion` - Linux kernel version - `ParityCheckSpeed` - Current parity check speed from var.ini - **Enhanced Network Information** via ethtool: - `SupportedPorts` - Supported port types (TP, AUI, MII, Fibre, etc.) - `SupportedLinkModes` - Supported link speeds and modes - `SupportedPauseFrame` - Pause frame support - `SupportsAutoNeg` - Auto-negotiation support - `SupportedFECModes` - Forward Error Correction modes - `AdvertisedLinkModes` - Advertised link speeds and modes - `AdvertisedPauseFrame` - Advertised pause frame use - `AdvertisedAutoNeg` - Advertised auto-negotiation - `AdvertisedFECModes` - Advertised FEC modes - `Duplex` - Duplex mode (Full/Half) - `AutoNegotiation` - Auto-negotiation status (on/off) - `Port` - Port type (Twisted Pair, Fibre, etc.) - `PHYAD` - PHY address - `Transceiver` - Transceiver type (internal/external) - `MDIX` - MDI-X status (on/off/Unknown) - `SupportsWakeOn` - Supported Wake-on-LAN modes - `WakeOn` - Current Wake-on-LAN setting - `MessageLevel` - Driver message level - `LinkDetected` - Link detection status - `MTU` - Maximum Transmission Unit - **New Libraries**: - `daemon/lib/dmidecode.go` - Parser for dmidecode output (SMBIOS/DMI types 0, 2, 4, 7, 16, 17) - `daemon/lib/ethtool.go` - Parser for ethtool output with comprehensive network interface details - **New DTOs**: - `HardwareInfo` - Container for all hardware information - `BIOSInfo` - BIOS/UEFI information - `BaseboardInfo` - Motherboard/baseboard information - `CPUHardwareInfo` - CPU hardware specifications - `CPUCacheInfo` - CPU cache level information - `MemoryArrayInfo` - Memory array/controller information - `MemoryDeviceInfo` - Individual memory module information ### Changed - **System Collector**: Enhanced with virtualization and additional system information - Added `isHVMEnabled()` method to detect hardware virtualization support - Added `isIOMMUEnabled()` method to detect IOMMU support - Added `getOpenSSLVersion()` method to retrieve OpenSSL version - Added `getKernelVersion()` method to retrieve kernel version - Added `getParityCheckSpeed()` method to parse parity check speed from var.ini - **Network Collector**: Enhanced with ethtool integration - Added `enrichWithEthtool()` method to populate network interface details - Network information now includes comprehensive ethtool data when available - Gracefully handles cases where ethtool is not available or fails - **Orchestrator**: Updated to manage hardware collector - Increased collector count from 9 to 10 - Hardware collector initialized and started with 5-minute interval - **API Server**: Updated to cache and serve hardware information - Added `hardwareCache` field to Server struct - Subscribed to `hardware_update` events - Hardware events broadcast to WebSocket clients --- ## [2025.11.11] - 2025-11-08 ### Fixed - **VM CPU Percentage Tracking**: Implemented proper CPU percentage calculation for VMs - Added historical tracking to VM collector using `cpuStats` struct with mutex protection - Guest CPU % now calculated from `virsh domstats` CPU time deltas over time intervals - Host CPU % now calculated from QEMU process CPU usage via `/proc/[pid]/stat` - CPU percentages are calculated as: `(current_time - previous_time) / time_interval / num_vcpus * 100` - Percentages are clamped to valid range [0, 100] to handle edge cases - CPU stats history is automatically cleared when VMs are shut off - First measurement after VM start returns 0% (requires two measurements for delta calculation) - Subsequent measurements return accurate real-time CPU percentages - Host CPU % matches the percentage shown in `ps`/`top` for the QEMU process - Guest CPU % represents the percentage of allocated vCPUs being used inside the guest OS ### Changed - **VM Collector**: Enhanced with CPU tracking infrastructure - Added `cpuStats` struct to store guest CPU time, host CPU time, and timestamp - Added `previousStats` map with mutex for thread-safe historical tracking - Added `getGuestCPUTime()` method using `virsh domstats --cpu-total` - Added `getHostCPUTime()` method reading `/proc/[pid]/stat` - Added `getQEMUProcessPID()` method using `pgrep` to find QEMU process - Added `clearCPUStats()` method to reset tracking when VMs are shut off - Updated `getVMCPUUsage()` to accept `numVCPUs` parameter and calculate real percentages ### Removed - **Placeholder CPU Values**: Removed the "needs historical data" limitation - CPU percentage fields now return real values instead of always returning 0 - Removed placeholder comments about needing historical data implementation --- ## [2025.11.10] - 2025-11-08 ### Added - **Enhanced VM Statistics**: Added comprehensive VM monitoring metrics - Guest CPU usage percentage (placeholder for future implementation with historical data) - Host CPU usage percentage (placeholder for future implementation with historical data) - Memory display in human-readable format (e.g., "1.17 GB / 4.00 GB") - Disk I/O statistics: total read and write bytes across all VM disks - Network I/O statistics: total RX and TX bytes across all VM network interfaces - New DTO fields: `guest_cpu_percent`, `host_cpu_percent`, `memory_display`, `disk_read_bytes`, `disk_write_bytes`, `network_rx_bytes`, `network_tx_bytes` - **Enhanced Docker Container Statistics**: Added comprehensive container monitoring metrics - Container version extracted from image tag - Network mode (e.g., "bridge", "host", "none") - Container IP address - Port mappings in "host_port:container_port" format - Volume mappings with host path, container path, and mode (rw/ro) - Restart policy (e.g., "always", "unless-stopped", "on-failure", "no") - Container uptime in human-readable format (e.g., "2d 5h 30m", "3h 45m", "15m") - Memory display in human-readable format (e.g., "512.00 MB / 2.00 GB") - New DTO fields: `version`, `network_mode`, `ip_address`, `port_mappings`, `volume_mappings`, `restart_policy`, `uptime`, `memory_display` ### Changed - **VM Collector**: Enhanced data collection using additional virsh commands - Added `getVMCPUUsage()` method using `virsh cpu-stats` (returns 0 pending historical data implementation) - Added `getVMDiskIO()` method using `virsh domblklist` and `virsh domblkstat` - Added `getVMNetworkIO()` method using `virsh domiflist` and `virsh domifstat` - Added `formatMemoryDisplay()` helper for human-readable memory formatting - **Docker Collector**: Enhanced data collection using docker inspect - Added `getContainerDetails()` method using `docker inspect` for comprehensive container metadata - Added `formatUptime()` helper for human-readable uptime formatting - Added `formatMemoryDisplay()` helper for human-readable memory formatting - Container details now include network configuration, volume mappings, and restart policies --- ## [2025.11.9] - 2025-11-08 ### Fixed - **VM Collector**: Fixed parsing of VM names containing spaces - Changed from parsing `virsh list --all` column-based output to using `virsh list --all --name` - Added `getVMState()` helper method to get VM state using `virsh domstate ` - Added `getVMID()` helper method to get VM ID using `virsh domid ` - Now correctly handles VM names with spaces, hyphens, underscores, and special characters - Example: "Windows Server 2016" is now correctly parsed instead of being split into "Windows" and "Server 2016 running" - **VM Control API**: Fixed VM control endpoints to work with VM names containing spaces - Updated VM name validation regex to allow spaces: `^[a-zA-Z0-9 _.-]{1,253}$` - Fixed route parameter mismatch: changed VM control routes from `{id}` to `{name}` - VM control endpoints now correctly accept URL-encoded spaces (e.g., `Windows%20Server%202016`) - All VM operations (start, stop, restart, pause, resume, hibernate, force-stop) now work with spaces in VM names --- ## [2025.11.8] - 2025-11-08 ### Added - **User Scripts API**: New REST API endpoints for discovering and executing Unraid User Scripts - GET `/api/v1/user-scripts` - List all available user scripts with metadata - POST `/api/v1/user-scripts/{name}/execute` - Execute a user script with background/wait options - Supports reading script descriptions from the `description` file - Includes path traversal protection and input validation - Returns script metadata: name, description, path, executable status, last modified timestamp - Execution options: `background` (default: true), `wait` (default: false) - Enables automation tools like Home Assistant to remotely execute Unraid maintenance scripts --- ## [2025.11.7] - 2025-11-08 ### Changed - **README.md Simplified**: Reduced README.md to essential information only - Removed detailed feature lists, API endpoints, and support links - Kept only the plugin name heading and brief description - Maintains proper display name format for Plugin Manager - Reduces file size while preserving functionality --- ## [2025.11.6] - 2025-11-08 ### Fixed - **Plugin Display Name**: Plugin now displays as "Unraid Management Agent" in the Unraid Plugin Manager instead of "unraid-management-agent" - Added README.md file to plugin directory with proper display name formatting - Follows Unraid plugin naming conventions used by Community Applications and other established plugins - Settings menu display name remains unchanged (was already correct) - Improves user experience and plugin discoverability in the Plugin Manager --- ## [2025.11.5] - 2025-11-08 ### Added - **USB Flash Drive Detection**: Plugin now detects USB flash drives (including the Unraid boot drive) and skips SMART data collection - Checks device sysfs path to identify USB transport - Detects Unraid boot drive by checking if device is mounted at `/boot` - Avoids unnecessary SMART commands on devices that don't support SMART monitoring - SMART status remains "UNKNOWN" for USB flash drives (consistent with previous behavior) - Adds debug logging to indicate when USB flash drive detection occurs ### Changed - **NVMe-Specific SMART Collection**: Optimized SMART data collection for NVMe drives - NVMe drives are now detected by checking device name pattern (e.g., `nvme0n1`) - NVMe drives skip the `-n standby` flag since they don't support standby mode - Uses `smartctl -H /dev/{device}` for NVMe drives (without `-n standby`) - SATA/SAS drives continue to use `smartctl -n standby -H /dev/{device}` (existing behavior) - Adds debug logging to indicate device type detection (NVMe vs SATA/SAS) - Improves efficiency by avoiding unnecessary standby checks on NVMe drives --- ## [2025.11.4] - 2025-11-08 ### Fixed - **CRITICAL FIX**: Disk spin-down compatibility - Plugin now respects Unraid's disk spin-down settings - Changed SMART data collection to use `smartctl -n standby` flag - Disks in standby mode are no longer woken up for SMART health checks - Previous implementation was preventing disks from spinning down by accessing them every 30 seconds - SMART status is now only collected when disks are already active - Preserves power savings and reduces disk wear for users with spin-down configured - Fixes critical issue where plugin prevented Unraid's disk spin-down functionality from working --- ## [2025.11.3] - 2025-11-08 ### Changed - Improved plugin UI/UX in Unraid interface - Added clickable settings link to plugin icon - Updated settings page title to "Unraid Management Agent" - Added server icon to plugin listing ### Fixed - Settings page URL now uses correct lowercase-with-hyphens format - Changed from `/Settings/UnraidManagementAgent` to `/Settings/unraid-management-agent` - Plugin icon now navigates to the correct settings page URL - **CRITICAL FIX**: SMART health status now correctly retrieved by running `smartctl -H` directly - Previous implementation tried to read from Unraid's cached files which use disk names instead of device names - Cached files also don't include the health status line - Now executes `smartctl -H /dev/{device}` to get actual health status - Fixes issue where all disks showed `smart_status: "UNKNOWN"` (Issue #4) --- ## [2025.11.2] - 2025-11-07 ### Added - Automated CI/CD workflow for releases using GitHub Actions - Automatically builds release package when Git tag is pushed - Calculates MD5 checksum and includes in release notes - Creates GitHub release with .tgz file attached - Extracts release notes from CHANGELOG.md - Supports pre-release detection (alpha, beta, rc versions) ### Fixed - SMART health status now correctly reported in `/api/v1/disks` endpoint (#4) - Parses actual SMART health status from Unraid's cached smartctl output - Returns "PASSED" for healthy disks (SATA/SAS drives) - Returns "PASSED" for healthy NVMe drives (normalizes "OK" to "PASSED") - Returns actual status values like "FAILED" when SMART tests fail - No longer returns "UNKNOWN" for all disks when SMART data is available --- ## [2025.11.1] - 2025-11-07 ### Added - Docker vDisk usage monitoring in `/api/v1/disks` endpoint (#2) - Automatically detects Docker vDisk at `/var/lib/docker` mount point - Reports size, used, free bytes, and usage percentage - Identifies vDisk file path (e.g., `/mnt/user/system/docker/docker.vdisk`) - Includes filesystem type detection - Assigned role `docker_vdisk` for easy filtering - Enables monitoring of Docker storage capacity for alerts - Log filesystem usage monitoring in `/api/v1/disks` endpoint (#3) - Automatically detects log filesystem at `/var/log` mount point - Reports size, used, free bytes, and usage percentage - Identifies device name (e.g., `tmpfs` for RAM-based log storage) - Includes filesystem type detection (tmpfs, ext4, xfs, etc.) - Assigned role `log` for easy filtering - Enables monitoring of log storage capacity to prevent system failures - Critical for tmpfs-based log filesystems that can fill up and cause issues ### Fixed - UPS API endpoint now returns actual UPS model name instead of hostname (#1) --- ## [2025.11.0] - 2025-11-03 ### Added #### Enhanced System Information Collector - **CPU Model Detection**: Automatic detection of CPU model, cores, threads, and frequency from `/proc/cpuinfo` - **BIOS Information**: Server model, BIOS version, and BIOS release date via `dmidecode` - **Per-Core CPU Usage**: Individual CPU core usage monitoring with `cpu_per_core_usage` field - **Server Model Identification**: Hardware model detection for better system identification #### Detailed Disk Metrics - **I/O Statistics**: Read/write operations and bytes per disk from `/sys/block/{device}/stat` - `read_ops` - Total read operations - `read_bytes` - Total bytes read - `write_ops` - Total write operations - `write_bytes` - Total bytes written - `io_utilization_percent` - Disk I/O utilization percentage - **Disk Spin State Detection**: Enhanced spin state detection (active, standby, unknown) - **Per-Disk Performance Metrics**: Real-time performance monitoring for each disk ### Changed #### Documentation Updates - **README.md Roadmap Reorganization**: - Added "Recently Implemented ✅" section to highlight completed features - Moved Enhanced System Info Collector and Detailed Disk Metrics from planned to implemented - Updated "Planned Enhancements" to only include outstanding features - Added detailed sub-items for each implemented feature - **Third-Party Plugin Notice**: Added prominent disclaimer distinguishing this plugin from official Unraid API - **System Compatibility Section**: Added hardware compatibility notice and tested configuration details - **Contributing Guidelines**: Expanded contribution workflow for hardware compatibility fixes - **Version References**: Updated from Unraid 6.x to 7.x throughout documentation #### Configuration Management - **Log Rotation**: Implemented automatic log rotation with 5 MB max file size (using lumberjack.v2) - **Log Level Support**: Added configurable log levels (DEBUG, INFO, WARNING, ERROR) with `--log-level` CLI flag - **Default Log Level**: Set to WARNING for production to minimize disk usage - **Configuration File Management**: Improved config file creation and synchronization - **Auto-Start Behavior**: Service now always starts automatically when Unraid array starts (removed toggle option) ### Fixed - **Configuration Synchronization**: Fixed LOG_LEVEL not being read from config file - **Start Script**: Now properly creates config directory and default config file - **Deployment Script**: Updated to use start script instead of bypassing configuration ### Testing - **Test Suite**: All 66 tests passing across 3 packages (100% pass rate) - **Deployment Verification**: Successfully deployed and verified on Unraid 7.x server --- ## [2025.10.03] - Initial Release ### Added #### Phase 1 & 2 API Enhancements **Phase 1.1: Array Control Operations** - `POST /api/v1/array/start` - Start the Unraid array - `POST /api/v1/array/stop` - Stop the Unraid array - `POST /api/v1/array/parity-check/start` - Start parity check (with correcting option) - `POST /api/v1/array/parity-check/stop` - Stop parity check - `POST /api/v1/array/parity-check/pause` - Pause parity check - `POST /api/v1/array/parity-check/resume` - Resume parity check - `GET /api/v1/array/parity-check/history` - Get parity check history from log file - New `ParityCollector` to parse `/boot/config/parity-checks.log` - New `ParityCheckRecord` and `ParityCheckHistory` DTOs **Phase 1.2: Single Resource Endpoints** - `GET /api/v1/disks/{id}` - Get single disk by ID, device, or name - `GET /api/v1/docker/{id}` - Get single container by ID or name - `GET /api/v1/vm/{id}` - Get single VM by ID or name - Support for multiple identifier types (ID, name, device) - 404 error handling for missing resources **Phase 1.3: Enhanced Disk Details** - Added `serial_number` field to DiskInfo DTO - Added `model` field to DiskInfo DTO - Added `role` field to DiskInfo DTO (parity, parity2, data, cache, pool) - Added `spin_state` field to DiskInfo DTO (active, standby, unknown) - Automatic role detection based on disk name/ID - Spin state detection based on temperature - SMART data extraction for serial number and model **Phase 2.1: Read-Only Configuration Endpoints** - `GET /api/v1/shares/{name}/config` - Get share configuration - `GET /api/v1/network/{interface}/config` - Get network interface configuration - `GET /api/v1/settings/system` - Get system settings - `GET /api/v1/settings/docker` - Get Docker settings - `GET /api/v1/settings/vm` - Get VM Manager settings - New `ShareConfig`, `NetworkConfig`, `SystemSettings`, `DockerSettings`, `VMSettings` DTOs - New `ConfigCollector` with parsers for all config files **Phase 2.2: Configuration Write Endpoints** - `POST /api/v1/shares/{name}/config` - Update share configuration - `POST /api/v1/settings/system` - Update system settings - Automatic backup creation before config updates (.bak files) - JSON request body validation - Error handling for write operations #### Disk Settings Feature - `GET /api/v1/settings/disks` - Get global disk settings - New `DiskSettings` DTO with fields: - `spindown_delay_minutes` - Default spin down delay (critical for Home Assistant) - `start_array` - Auto start array on boot - `spinup_groups` - Enable spinup groups - `shutdown_timeout_seconds` - Shutdown timeout - `default_filesystem` - Default filesystem type - Reads from `/boot/config/disk.cfg` - Enables Home Assistant to avoid waking spun-down disks #### Documentation - Complete API reference guide (`docs/api/API_REFERENCE.md`) - API coverage analysis (`docs/api/API_COVERAGE_ANALYSIS.md`) - WebSocket events documentation (`docs/WEBSOCKET_EVENTS_DOCUMENTATION.md`) - WebSocket event structure guide (`docs/WEBSOCKET_EVENT_STRUCTURE.md`) - Phase 1 & 2 implementation report (`docs/implementation/PHASE_1_2_IMPLEMENTATION_REPORT.md`) - Disk settings implementation report (`docs/implementation/DISK_SETTINGS_IMPLEMENTATION.md`) - Deployment guides (`docs/deployment/`) - Documentation index (`docs/README.md`) #### Core Features - Comprehensive system monitoring (CPU, memory, uptime, temperature) - Array status monitoring (state, parity, disk counts) - Per-disk metrics (SMART data, temperature, space usage, spin state) - Network interface monitoring (bandwidth, IP addresses, MAC addresses) - Docker container monitoring and control - Virtual machine monitoring and control - UPS status monitoring - GPU metrics monitoring - User share monitoring - REST API with 46 endpoints - WebSocket support with 9 event types - Event-driven architecture with pubsub - Graceful shutdown and panic recovery - Automatic log rotation ### Changed - Updated deployment script to include `--port 8043` flag - Improved error handling in collectors - Enhanced logging with structured messages - Optimized data collection intervals ### Fixed - Fixed logger method calls (changed `logger.Warn` to `logger.Debug`) - Fixed API server port configuration - Fixed icon display in Unraid Plugins page - Fixed backup creation in deployment script ### Deployment - Deployed to live Unraid server (192.168.20.21) - All endpoints tested and verified - Service running on port 8043 - Icon fix verified in Unraid UI --- ## API Endpoint Summary ### Total Endpoints: 46 **Monitoring** (13): - GET /api/v1/health - GET /api/v1/system - GET /api/v1/array - GET /api/v1/disks - GET /api/v1/disks/{id} - GET /api/v1/shares - GET /api/v1/docker - GET /api/v1/docker/{id} - GET /api/v1/vm - GET /api/v1/vm/{id} - GET /api/v1/ups - GET /api/v1/gpu - GET /api/v1/network **Control** (19): - POST /api/v1/docker/{id}/start - POST /api/v1/docker/{id}/stop - POST /api/v1/docker/{id}/restart - POST /api/v1/docker/{id}/pause - POST /api/v1/docker/{id}/unpause - POST /api/v1/vm/{id}/start - POST /api/v1/vm/{id}/stop - POST /api/v1/vm/{id}/restart - POST /api/v1/vm/{id}/pause - POST /api/v1/vm/{id}/resume - POST /api/v1/vm/{id}/hibernate - POST /api/v1/vm/{id}/force-stop - POST /api/v1/array/start - POST /api/v1/array/stop - POST /api/v1/array/parity-check/start - POST /api/v1/array/parity-check/stop - POST /api/v1/array/parity-check/pause - POST /api/v1/array/parity-check/resume - GET /api/v1/array/parity-check/history **Configuration** (13): - GET /api/v1/shares/{name}/config - POST /api/v1/shares/{name}/config - GET /api/v1/network/{interface}/config - GET /api/v1/settings/system - POST /api/v1/settings/system - GET /api/v1/settings/docker - GET /api/v1/settings/vm - GET /api/v1/settings/disks **WebSocket** (1): - GET /api/v1/ws --- ## API Coverage | Category | Coverage | Status | | ------------------ | -------- | ---------- | | **Overall** | **60%** | 🟡 Partial | | Monitoring | 85% | ✅ Good | | Control Operations | 75% | ✅ Good | | Configuration | 40% | 🟡 Partial | | Administration | 0% | 🔴 None | --- ## WebSocket Events **Total Event Types**: 9 - `system` - System metrics updates - `array` - Array status changes - `disk` - Disk status changes - `docker` - Docker container events - `vm` - VM state changes - `ups` - UPS status updates - `gpu` - GPU metrics updates - `network` - Network statistics updates - `share` - Share information updates --- ## Known Issues None at this time. --- ## Planned Features ### Phase 3: Advanced Configuration - Network configuration write endpoint - Docker settings write endpoint - VM settings write endpoint - Per-disk spindown override settings ### Phase 4: User Management - User list endpoint - User permissions endpoint - User creation/modification endpoints ### Phase 5: Plugin Management - Plugin list endpoint - Plugin install/update/remove endpoints - Plugin settings endpoints ### Future Enhancements - Historical data storage - Alerting and notification system - Network statistics trending - Enhanced SMART attribute monitoring --- ## Migration Guide ### From Pre-1.0.0 Versions This is the initial release. No migration required. --- ## Contributors - Ruaan Deysel (@ruaan-deysel) --- ## Links - **GitHub Repository**: - **Documentation**: [docs/README.md](docs/README.md) - **API Reference**: [docs/api/API_REFERENCE.md](docs/api/API_REFERENCE.md) - **Issues**: --- **Last Updated**: 2025-11-03 **Current Version**: 2025.11.0