Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
52828f8
fix(install): restart on update, keep the env file, offline install, …
btouchard Sep 30, 2026
e2bc989
fix(updates): check official images, real rollbacks, apply update labels
btouchard Sep 30, 2026
6b4492c
fix(http): refuse cross-origin API writes, unbuffer SSE, explain TLS …
btouchard Sep 30, 2026
924eefd
fix(deploy): make the image, entrypoint and Kubernetes manifests work…
btouchard Sep 30, 2026
3c28564
fix(mcp): serve /mcp behind a reverse proxy on the same host
btouchard Sep 30, 2026
b41ab8f
fix(status): send status page emails through the environment SMTP server
btouchard Sep 30, 2026
61a1260
fix(alerts): escalation timing and ack fan-out, drop inert alert fields
btouchard Sep 30, 2026
378cf84
fix(agent): keep replays out of live state, re-enroll refused agents,…
btouchard Sep 30, 2026
0c498e5
fix(kubernetes): live events, loss detection and a Docker fallback fo…
btouchard Sep 30, 2026
85d72e3
fix(status): answer subscriptions alike and encode mail subjects
btouchard Sep 30, 2026
9374614
fix: persist daily uptime, honour ignore and Swarm service labels, ad…
btouchard Sep 30, 2026
863557e
fix(alerts): keep trigger state and policy scopes on update, show tog…
btouchard Sep 30, 2026
4798dde
fix(kubernetes): swap the API clients atomically on reconnect
btouchard Sep 30, 2026
f785c23
fix(kubernetes): list Services through the connection's client accessor
btouchard Sep 30, 2026
fc8ca37
docs: align status page, MCP, escalation and security pages with the …
btouchard Sep 30, 2026
7268182
docs: bring the security policy and security guide in line with the code
btouchard Sep 30, 2026
b9f52e8
docs: rewrite the API reference and architecture pages from the code,…
btouchard Sep 30, 2026
eed3c7e
fix(updates): track digest updates against the running image, apply u…
btouchard Sep 30, 2026
04301c9
docs: bring install, configuration and index pages in line with the code
btouchard Sep 30, 2026
0e3f6dc
docs: align monitors, multi-host, agent and PostgreSQL pages with the…
btouchard Sep 30, 2026
b05a95c
docs: rewrite troubleshooting and correct the cloud provider guides
btouchard Sep 30, 2026
20291fa
fix(alerts): raise Kubernetes alerts, honour ignore on Swarm services…
btouchard Sep 30, 2026
00ec78c
fix(swarm): node alert ids, update alert recovery, resumed tracking, …
btouchard Sep 30, 2026
036c98e
fix(swarm): resolve the quorum alert once the managers are back
btouchard Sep 30, 2026
ad9e341
fix(monitoring): resolve every certificate and resource alert, carry …
btouchard Sep 30, 2026
eeac376
fix: status page, escalation, posture, SMTP, SSE and config hardening
btouchard Sep 30, 2026
1c06f6d
fix(status): keep a maintenance window's components when editing it
btouchard Sep 30, 2026
423f572
docs: align Swarm, Kubernetes and label guides with the audited code
btouchard Sep 30, 2026
fe86947
fix(api): wire endpoint events, track webhook deliveries, apply live …
btouchard Sep 30, 2026
fc280bb
fix(mcp): check component ids before writing an incident or a window
btouchard Sep 30, 2026
09e88b7
fix(alerts): keep each notification stream in order, announce status …
btouchard Sep 30, 2026
eb01876
fix(status): update the public page in place from status events
btouchard Sep 30, 2026
d1699e0
fix(swarm): one replica alert path, cluster-wide crash loops, filled …
btouchard Sep 30, 2026
738b2ec
style: gofmt the files that had drifted
btouchard Sep 30, 2026
03cbedc
docs: align the alert engine, update intelligence and README with the…
btouchard Sep 30, 2026
6a1b4b0
fix(updates): keep unscanned updates, analyse every container, update…
btouchard Sep 30, 2026
5f61cb3
docs: align the API reference, security, install, troubleshooting and…
btouchard Sep 30, 2026
11a2911
docs: align the feature pages, index and README with the audited code
btouchard Sep 30, 2026
60f1f58
fix(agent): start without a container runtime, and close the last con…
btouchard Sep 30, 2026
2d8011f
fix(store): stop the SQLite writer without racing in-flight writes
btouchard Sep 30, 2026
ba059ac
fix(status): keep hidden components off the public stream and harden …
btouchard Sep 30, 2026
4669b45
feat(agent): report the detected runtime after enrollment
btouchard Sep 30, 2026
d918fd7
fix(runtime): degrade on Docker loss and stop the Swarm manager when …
btouchard Sep 30, 2026
8f19880
test(app): name the sentinel PostgreSQL role after the whole UUID
btouchard Sep 30, 2026
dc96023
fix(alerts): share one acknowledgment path and keep acknowledged aler…
btouchard Sep 30, 2026
436b1a7
fix(status): never name a hidden component on a public surface
btouchard Sep 30, 2026
bf67158
fix: resolve audit findings on updates, certificates, resources, hear…
btouchard Sep 30, 2026
62c5b1f
docs: align documentation with the audit fixes on runtime, alerts, st…
btouchard Sep 30, 2026
0b923f8
fix(status): refuse a maintenance window that ends when it starts
btouchard Sep 30, 2026
23062dc
fix(logs): stream lines up to 1 MiB and report a broken stream as such
btouchard Sep 30, 2026
be704bd
fix(store): read the clock once per uptime, ranking and retention ope…
btouchard Oct 1, 2026
efb5362
fix(deps): update undici, brace-expansion and fast-uri for the latest…
btouchard Oct 1, 2026
af1b0e4
fix(editions): drop per-entity alert routing, which was never impleme…
btouchard Oct 1, 2026
751bd2d
fix(security): rebuild OAuth redirects from trusted parts, bound the …
btouchard Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
90 changes: 64 additions & 26 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
# maintenant Configuration
# ========================

# Operating mode: embedded (default, server + local runtime), server (accepts
# remote agents), or agent (reports to a server, no HTTP interface).
# Operating mode: embedded (default: web server and local runtime, which also
# accepts remote agents when the edition allows it), server (the same, but it
# refuses to start below the Personal edition), or agent (reports to a server,
# no HTTP interface).
# MAINTENANT_MODE=embedded

# Listen address (use 0.0.0.0 inside containers, 127.0.0.1 on host)
MAINTENANT_ADDR=127.0.0.1:8080

# Agent gRPC listener (server and embedded modes, Personal edition or above).
# Loopback by default: remote agents cannot connect until you change it. In a
# container use 0.0.0.0:8443 and publish the port.
MAINTENANT_GRPC_LISTEN=127.0.0.1:8443

# Log verbosity: debug, info (default), warn, error
# MAINTENANT_LOG_LEVEL=info

# SQLite database path
# SQLite database path. The container image sets /data/maintenant.db. Its
# directory also holds the license cache, the telemetry identity and the
# embedded agent's data, even when the database itself is PostgreSQL.
MAINTENANT_DB=./maintenant.db
# Optional: back the server data set with a PostgreSQL you already operate.
# Empty means SQLite, which is the default and the only agent storage.
Expand All @@ -22,12 +30,18 @@ MAINTENANT_DB=./maintenant.db
# Organisation name (displayed on the public status page)
# MAINTENANT_ORGANISATION_NAME=Acme Corp

# Public base URL (used for heartbeat ping URLs and subscriber links)
# Public base URL (heartbeat ping URLs, status page email links and the MCP
# OAuth issuer). Default: http://<MAINTENANT_ADDR>
# MAINTENANT_BASE_URL=https://maintenant.example.com
# gRPC URL shown to agents in the enrollment commands (default: derived from
# the Host of the web request)
# MAINTENANT_GRPC_URL=grpcs://maintenant-agents.example.com
# MAINTENANT_STATUS_URL=https://status.example.com (public)
# Canonical URL of the status page: reported as status_url by GET /api/v1/edition
# and opened by the admin link (default: the relative /status)
# MAINTENANT_STATUS_URL=https://status.example.com

# CORS allowed origins (comma-separated, empty = same-origin only)
# Origins allowed to call the API cross-origin and to send state-changing
# requests from a browser (comma-separated, empty = same-origin only)
# MAINTENANT_CORS_ORIGINS=http://localhost:5173

# Reverse proxies whose X-Forwarded-For / X-Real-IP / X-Forwarded-Host headers are
Expand All @@ -41,7 +55,8 @@ MAINTENANT_DB=./maintenant.db
# Create HTTP endpoints from Traefik and Caddy docker-proxy container labels (Docker only)
# MAINTENANT_PROXY_LABELS=true

# Max request body size in bytes (default: 1MB)
# Max request body size in bytes (default: 1048576). A value that is not a
# positive whole number stops the startup.
# MAINTENANT_MAX_BODY_SIZE=1048576

# Update intelligence scan interval (Go duration, default: 24h)
Expand All @@ -52,12 +67,13 @@ MAINTENANT_DB=./maintenant.db

# Raise an alert when a container has been stopped for this long (Go duration,
# e.g. 5m). Unset or 0 disables the check; a container that exited with code 0
# counts as finished, not down. The alert resolves on its own once it runs again.
# or 143 (or 137 when the out-of-memory killer was not the cause) counts as
# completed, not down. The alert resolves on its own once it runs again.
# MAINTENANT_CONTAINER_DOWN_AFTER=5m

# How long raw resource samples are kept (Go duration, default: 48h).
# The 24h chart is the longest range reading them — 7d is served from the hourly
# rollup — so 24h is the floor. Raw samples dominate database size.
# The 24h chart is the longest range reading them (7d is served from the hourly
# rollup), so 24h is the floor. Raw samples dominate database size.
# MAINTENANT_RETENTION_SNAPSHOTS=48h

# Time between two retention passes (Go duration, default: 1h, minimum 1m)
Expand All @@ -66,51 +82,69 @@ MAINTENANT_DB=./maintenant.db
# Rows deleted per transaction during retention (default: 1000, range 100-100000)
# MAINTENANT_RETENTION_BATCH_SIZE=1000

# Score below which the security posture raises an alert (unset = no alert)
# Score below which the security posture raises an alert, from 1 to 100,
# checked every 5 minutes (Personal edition; unset or 0 = no alert, a value outside 0-100 or
# not a number stops the startup)
# MAINTENANT_SECURITY_SCORE_THRESHOLD=70

# Opt out of anonymous usage telemetry
# MAINTENANT_DISABLE_TELEMETRY=true

# Allow notification webhooks pointing at private addresses (SSRF guard off).
# Only on a network where you trust every webhook target.
# Development only: allow http:// and private, loopback or link-local targets
# for notification channels and webhook subscriptions (SSRF guard off).
# Only on a network where you trust every target.
# MAINTENANT_ALLOW_PRIVATE_WEBHOOKS=true

# Kubernetes namespaces to monitor (comma-separated, empty = all)
# Kubernetes namespaces to monitor (comma-separated, empty = all except
# kube-system, kube-public and kube-node-lease)
# MAINTENANT_K8S_NAMESPACES=default,production

# Kubernetes namespaces to exclude (comma-separated)
# MAINTENANT_K8S_EXCLUDE_NAMESPACES=kube-system
# Kubernetes namespaces to exclude (comma-separated, added to the three above;
# ignored when an allowlist is set)
# MAINTENANT_K8S_EXCLUDE_NAMESPACES=monitoring

# Pro license key (enables Pro features)
# Personal or Pro license key
# MAINTENANT_LICENSE_KEY=your-license-key

# GitHub token used when fetching release notes for the changelog (Personal
# edition). Optional: it only raises the GitHub API rate limit.
# GITHUB_TOKEN=ghp_xxx

# MCP Server (Model Context Protocol for AI assistants)
# Both credentials are required: /mcp bypasses the reverse-proxy auth, so
# without them maintenant refuses to start rather than serving your monitoring
# data to anyone. Set MAINTENANT_MCP_ALLOW_UNAUTHENTICATED=true to accept that
# on a trusted network.
# on a trusted network. Use a secret of at least 32 characters
# (openssl rand -hex 32).
# MAINTENANT_MCP=true
# MAINTENANT_MCP_CLIENT_ID=maintenant-mcp
# MAINTENANT_MCP_CLIENT_SECRET=your-secret-here
# Loopback redirect URIs are always accepted; list the others exactly.
# MAINTENANT_MCP_ALLOWED_REDIRECT_URIS=https://claude.ai/api/mcp/auth_callback
# MAINTENANT_MCP_ALLOW_UNAUTHENTICATED=true

# SMTP configuration (required for email notification channels)
# SMTP configuration (email alert channel with Personal, status page
# subscribers with Pro). Port 465 uses implicit TLS; on other ports STARTTLS is
# used when the server announces it.
# MAINTENANT_SMTP_HOST=smtp.example.com
# MAINTENANT_SMTP_PORT=587
# MAINTENANT_SMTP_USERNAME=alerts@example.com
# MAINTENANT_SMTP_PASSWORD=secret
# MAINTENANT_SMTP_FROM=maintenant@example.com

# Extra root CA to trust, for hosts signed by an internal PKI (step-ca, AD CS...).
# Added to the system roots, never replacing them. Mount it readable by uid 65534.
# Added to the system roots, never replacing them. Applies to endpoint and
# certificate checks, webhooks and channels, outbound heartbeats, SMTP, the
# license server, registries and the agent's connection to its server. Mount it
# readable by uid 65534; an unreadable file stops the startup.
# Prefer this over SSL_CERT_FILE, which replaces the whole bundle and fails silently.
# MAINTENANT_CA_CERT=/etc/maintenant/ca.pem

# ── Multi-host ────────────────────────────────────────────────────────────────
# An agent needs the server URL and, on first boot only, an enrollment token.
# The token is consumed once; the agent then authenticates with its own key.
# An agent needs the server URL and, on first boot, an enrollment token. The
# token is consumed once; the agent then authenticates with its own key. If the
# server refuses the stored identity (agent revoked or deleted), the agent
# enrolls again with the token when it is still set, and stops otherwise.
# MAINTENANT_SERVER=grpcs://maintenant-agents.example.com:8443
# MAINTENANT_ENROLLMENT_TOKEN=paste-the-token-from-the-Agents-page
# MAINTENANT_LABEL=web-01
Expand All @@ -119,14 +153,17 @@ MAINTENANT_DB=./maintenant.db
# operating system. Left empty, the agent finds it from its own pod.
# MAINTENANT_NODE_NAME=node-01

# Where the agent keeps its identity and liveness files (agent mode)
# Where the agent keeps its identity, its spool database and its liveness file
# (agent mode)
# MAINTENANT_DATA_DIR=/var/lib/maintenant

# Skip TLS verification when reaching the server. Debug only: it defeats the
# point of TLS. Use MAINTENANT_CA_CERT for an internal PKI instead.
# MAINTENANT_GRPC_INSECURE_SKIP_TLS_VERIFY=true

# Server side: TLS for the gRPC listener agents connect to.
# Server side: TLS for the gRPC listener agents connect to. Set both or neither:
# one without the other stops the startup. With neither, a self-signed
# certificate is generated at each start and a warning is logged.
# MAINTENANT_GRPC_TLS_CERT=/etc/maintenant/grpc.crt
# MAINTENANT_GRPC_TLS_KEY=/etc/maintenant/grpc.key
# Serve gRPC as h2c instead, without TLS. Only behind a reverse proxy that
Expand All @@ -139,10 +176,11 @@ MAINTENANT_DB=./maintenant.db
# MAINTENANT_AGENT_STALE_THRESHOLD_SECONDS=60

# Agent side: local queue holding events while the server is unreachable.
# Setting both budgets to 0 disables it.
# Setting both the memory and the disk budget to 0 disables it. Events older
# than the age limit are dropped from it; 0 turns the limit off.
# MAINTENANT_AGENT_SPOOL_MAX_MEMORY_BYTES=16777216
# MAINTENANT_AGENT_SPOOL_MAX_DISK_BYTES=134217728
# MAINTENANT_AGENT_SPOOL_MAX_AGE_SECONDS=86400

# Also run a local agent alongside the server (server mode, Pro)
# Also run a local agent alongside the server (server mode, Personal edition or above)
# MAINTENANT_EMBEDDED_AGENT=true
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ RUN --mount=type=cache,target=/go/pkg/mod \

FROM alpine:3.21

RUN apk add --no-cache ca-certificates tzdata setpriv \
RUN apk upgrade --no-cache \
&& apk add --no-cache ca-certificates tzdata setpriv \
&& mkdir -p /data \
&& chown 65534:65534 /data

Expand All @@ -51,6 +52,9 @@ RUN apk add --no-cache ca-certificates tzdata setpriv \
# /tmp as a tiny tmpfs, which SQLITE_FULL-fails the conversion; /data has real space.
ENV SQLITE_TMPDIR=/data

# Its directory also holds the licence cache and the update window, PostgreSQL or not.
ENV MAINTENANT_DB=/data/maintenant.db

# Tells the OS identity reader it must not fall back to the image's own
# /etc/os-release, which describes the container rather than the host.
ENV MAINTENANT_CONTAINER=1
Expand Down
Loading
Loading