Skip to content

v1.5.0

Latest

Choose a tag to compare

@btouchard btouchard released this 09 Sep 10:31
0a7f227

Makes the monitor pages usable at fleet scale ? cards, dense rows or a sortable table, per page, remembered ? with instant search and status chips that filter instead of only counting. Adds a native Telegram channel and the MCP tools an assistant needs to set an alert up end to end. Carries a broad pass over security and code quality, from how a multi-host install identifies a container to what the vulnerability posture is allowed to claim.

Seeing a fleet

Containers, endpoints, certificates and heartbeats now share one toolbar: instant search, status chips that filter rather than only report a count, and the secondary filters behind a menu. Each page remembers how you left it ? cards when a dozen things need their own tile, dense rows when there are eighty, a sortable table when you want them ordered by a column. Rows and table carry a status gutter, so a long list reads at a glance instead of one card at a time.

Grouping stays a cards affordance. In rows it only added headers to scroll past, and in the table it meant one table per group, so sorting a column ordered within a group instead of across the fleet. Rows and table are one flat list now, and the group follows the container: a sortable column in the table, context beside the image tag in a row.

Top consumers becomes a foldable panel that keeps a one-line summary when put away.

Three defects surfaced on the way and are fixed with it: the archived-containers toggle refetched but the render kept filtering archived rows out, so it never showed anything; the pages declared a scroll container that never scrolled, which left the sticky toolbar inert; and in the light theme a hovered row turned into a grey slab, because --mnt-bg-hover sat on the border colour instead of one step under the surface.

Where a container is running now appears where it matters ? the detail view, the unified monitor rows, the tile tooltips ? and only when it tells you something: at least one agent enrolled, and no host scope already selected.

Telegram, natively

Telegram was reachable before only by pointing the generic webhook channel at it, which failed twice over: the payload shape is not what the API expects, and routing around it through a local relay ran into the SSRF guard, correctly. A telegram channel type removes both walls ? no URL to type, a fixed destination, a message the product formats, an optional forum topic. The bot token is write-only: it is stored, never returned by any response, and the interface shows only that one is on file.

It ships with the Personal edition, alongside email. Community keeps webhook and Discord: what is paid for is a channel ready to use, not the right to be alerted.

Setting up an alert from a conversation

The MCP server could create an alert trigger but had no way to obtain a channel id, and no way to know which edition was running ? so an assistant asked the operator for both. It now carries the channels (list_channels, get_channel, create_channel, update_channel, delete_channel, test_channel) with the same rules the REST API enforces, and get_edition, which reports the running edition, what each capability requires, quota usage and the history windows. Secrets are never returned. Writes broadcast on the SSE stream, so a channel created from a chat appears in an open interface without a reload.

Security and robustness

A broad pass over the code, with the multi-host path, the probes, the public HTTP surface and the vulnerability chain each tightened. The changes worth knowing about:

A container's identity is bound to the agent that reports it. The store has always keyed a container on (agent_id, external_id); every lookup, every archive and every update now does the same. Two hosts running the same image keep their own row, their own history and their own alerts, whatever they report. Rows written by earlier versions are reclaimed on the next report, so nothing has to be migrated or re-enrolled.

A probe never sends anything to a peer it could not verify. When a certificate is rejected, telling "degraded" from "down" is done with a bare TLS handshake ? enough to know the host answers and to capture the chain for expiry monitoring, without the request, its headers or its URL leaving the process.

Forwarded headers are believed only from a proxy you declared. MAINTENANT_TRUSTED_PROXIES names the addresses a reverse proxy connects from; X-Forwarded-For, X-Real-IP and X-Forwarded-Host are read from those and nowhere else, and the agent enrolment URL can no longer be steered by a header. See the upgrade note below: this one may need a setting on your side.

The vulnerability posture reports what it measured. Advisory records are fetched per identifier rather than inferred, the CVSS base score is computed from the v3.1 specification, and analysis runs for any container whose package resolves instead of only those with a pending update. What the analysis concluded is stored ? evaluated, not covered, or failed ? so a container that was never examined reads as such instead of scoring a perfect hundred.

Authorization codes and refresh tokens are single-use, in the store. Consuming one is a single conditional write decided by the row it touches, on either database engine, so two simultaneous exchanges cannot both succeed and no token pair is issued until the previous one is genuinely spent.

Alongside these: remote Docker healthchecks now reach the server, the last container removed from a host is archived instead of lingering, reconnecting an agent no longer disconnects it, the public status page bootstraps under its own CSP, a busy port fails startup instead of leaving a process with no HTTP surface, request bodies are bounded on every route that can carry one, and a fresh clone builds without help.

Dependency advisories cleared along the way: gRPC for CVE-2026-84304, golang.org/x/crypto for CVE-2026-56854, and the dev-only @humanfs/node for GHSA-p498-v437-472g.

The test suite lost its timing assumptions in the process: the alert engine's tests wait for the condition they assert on rather than for a fixed delay, which removes a class of intermittent CI failures without making the suite slower.

Upgrading

One migration, 31, adds the CVE analysis state. It runs on both engines at startup, as usual.

Three behaviours change, deliberately:

  • Forwarded headers are ignored unless you declare your proxies. Set MAINTENANT_TRUSTED_PROXIES (comma-separated CIDRs or IPs, --trustedProxies) to the address your reverse proxy connects from. Left unset ? the default ? every quota counts against the connecting address, which behind a proxy is the proxy itself: all clients then share one bucket. This is the fix, not a regression, but an instance that relied on per-client quotas needs the setting.
  • A degraded endpoint no longer carries an HTTP status. The probe no longer resends the request, so it knows the host answers TLS and nothing more. Uptime, alerting and the degraded status itself are unchanged.
  • The CVE category no longer scores 100 by default. A container that was never analysed reads "not evaluated" and makes the posture score partial; one whose image the data source does not cover reads "not covered". Some scores will drop. They were never earned.

The MCP list_cve tool, asked about a single container, now answers {container_id, evaluation, cves} instead of a bare array. The array is unchanged under cves.