▄▄▄▄▄   ▄▄▄▄▄ ▄▄▄▄▄▄▄▄▄     ▄▄▄▄▄▄▄▄     ▄▄▄        ▄▄▄▄▄▄      ▄▄▄▄▄▄▄▄▄▄▄▄▄    
          ▄▄▄ ▀▀▄    ▄▄ ▀▀▄▄▀  ▀▄   ▄▀▀ ▄▄ ▀▀▄    ▄▄▄  ▄▄▄ ▀▀▄ 
             ▀▄▀▄      ▀▄▀▄  ▄▄ ▀▄   ▄▀▄▀  ▀▄▀▄  ▀▀▀       ▀▄  
 ▀▄▀▄                                           ▀▀▀▀▀
 ▀▄  ▀▀▀       ▀▀▀▀▀                                                  
   ▀▀▀▀▀     ▀▀▀                                      
▀▀▀                                ▀▄  ▄▀                   
  ▀▀▀▀  ▄▀                      ▀▄▄  ▀▀  ▄▄▀                 
▀▀▀▀▀▀▀▀▀▀    ▀▀▀▀▀   ▀▀▀▀▀ ▀▀▀▀▀  ▀▀▀▀▀  ▀▀▀▀▀     ▀▀▀▀▀▀           ▀▀▀▀        

YAMOT — Yet Another Monitoring Tool

Monitor remote Linux servers, Proxmox VE hosts, and SNMP devices. No agent to install on the target machines.

Screenshots

Dashboard overview
Dashboard — server cards with live metrics and sparklines
Server detail
Server detail — charts, processes, services, disks
Fleet dashboard
Fleet dashboard — aggregated CPU, memory, and uptime across all servers

License: PolyForm Noncommercial 1.0.0 Copyright (c) 2026 Uberwald. All rights reserved. Commercial use requires a separate license from Uberwald. Send email to contact@uberwald.me

Features

  • Agentless monitoring — connects to remote servers via SSH; SNMP devices are polled via UDP
  • SSH key-based authentication only — password-based SSH auth is not supported; all monitored servers must use SSH private keys for authentication
  • SNMP v2c support — poll network devices (switches, routers, UPS, printers) via SNMPv2c with configurable community string and OIDs; no SSH required for SNMP devices
  • SSH host key verification (TOFU) — host keys are recorded on first connection and verified on all subsequent connections to prevent MITM attacks
  • 19 built-in monitors — CPU, memory, disk, network, load average, uptime, login activity, processes, services, SMART disk health, CPU temperature, custom checks, SNMP, plus 5 Proxmox VE monitors
  • Custom check scripts — run user-defined shell commands on remote servers via SSH and parse the output as a number or JSON; metrics are exposed as custom.<name> for alerting
  • HTTP/URL health checks — standalone HTTP/HTTPS endpoint checks that run from the YAMOT host (no SSH required); monitors status code, response time, and body match
  • SSL certificate and domain expiration checks — standalone monitors that check SSL/TLS certificate expiry via TLS and domain name expiry via WHOIS (no SSH required); runs once per day by default
  • Fleet dashboard — aggregated view of CPU, memory, disk, and uptime across all servers with group filtering and per-server alert counts
  • Proxmox VE support — 5 additional monitors for Proxmox hosts: cluster status, VM inventory, container inventory, storage pools, node status
  • Alerting — threshold-based rules with severity levels, cooldown, and server scoping. Multiple notification channels (email/SMTP, Slack, Mattermost) can be configured and enabled independently
  • Alert acknowledgment — acknowledge active alerts with a note to suppress repeated notifications; unacknowledge to resume
  • Maintenance windows — suppress alert evaluation during planned downtime for a specific server, a group, or all servers
  • Default alert templates — alert rules are automatically created when a server is added, with sensible thresholds for Linux (10 rules), Proxmox VE (13 rules), and SNMP devices (1 rule). Templates can be re-applied to existing servers from the alerts page
  • Server unreachable detection — automatically fires a critical alert when all monitors for a server fail, and resolves it when connectivity is restored
  • Web dashboard — real-time view of all servers with server detail pages, charts, sparklines, and Socket.io live updates
  • Server groups — organize servers into groups for scoped alert rules
  • Reports — historical data analysis with JSON export
  • Uptime percentage — rolling uptime tracking per server, exposed via API and displayed on the dashboard
  • Service health checks — monitor systemd services on remote servers with configurable watch lists
  • Process monitoring — top processes by CPU and memory usage on server detail pages
  • RBAC — three roles (admin, operator, viewer) with granular permissions
  • LDAP/ActiveDirectory — authenticate users against an LDAP or ActiveDirectory server; LDAP users are auto-provisioned on first login with the viewer role
  • CLI — manage servers, alerts, users, and generate reports from the terminal
  • Credential encryption — SSH private keys and passphrases encrypted at rest with AES-256-GCM
  • CSRF protection — double-submit cookie pattern for all state-changing API requests
  • Account lockout — accounts are temporarily locked after 5 consecutive failed login attempts
  • JWT revocation — tokens are invalidated when passwords or roles change via a token version mechanism
  • Rate limiting — global API rate limiting and per-IP login rate limiting
  • SQLite storage — zero external database dependency, single-file deployment
  • Offline operation — all frontend libraries (Font Awesome, Chart.js) vendored locally; no CDN dependencies required

Requirements

  • Node.js >= 18
  • SSH key-based access to the target servers (password authentication is not supported)
  • SNMP v2c enabled on network devices (for SNMP monitoring)

Quick Start

# Clone and install
git clone <repo-url> yamot
cd yamot
npm install

# Copy and edit configuration
cp .env.example .env
# Edit .env: set JWT_SECRET and ENCRYPTION_KEY to random values

# Compile LESS to CSS
npm run less

# Frontend libraries (Font Awesome, Chart.js) are vendored in public/vendor/
# — no internet connection or CDN required at runtime

# Start the server
npm start

On first boot, YAMOT creates a default admin user and writes a randomly generated password to a file at data/.initial-admin-password. Delete this file after noting the password.

Open http://localhost:3000 in your browser and log in with admin and the generated password.

Production deployment with PM2

PM2 is a process manager that keeps YAMOT running, restarts it on crashes, and starts it on boot.

# Install PM2 globally
npm install -g pm2

# Start YAMOT with PM2
pm2 start src/index.js --name yamot

# Save the process list so YAMOT restarts on reboot
pm2 save

# Enable PM2 startup script (follow the printed instructions)
pm2 startup

# View logs
pm2 logs yamot

# Restart after updates
pm2 restart yamot

# Stop
pm2 stop yamot

Make sure .env is configured with production values (JWT_SECRET, ENCRYPTION_KEY, NODE_ENV=production) before starting under PM2.

Configuration

All configuration is via environment variables, loaded from a .env file at the project root.

Variable Default Description
YAMOT_PORT 3000 Web server port
YAMOT_HOST 0.0.0.0 Web server bind address
NODE_ENV development production enforces secure defaults
DB_PATH ./data/yamot.db SQLite database file path
JWT_SECRET change-me-... JWT signing secret (must be changed in production)
JWT_EXPIRES_IN 24h JWT token lifetime
ENCRYPTION_KEY default-... AES-256-GCM key for SSH credential encryption (must be changed in production)
SSH_DEFAULT_PORT 22 Default SSH port
SSH_CONNECTION_TIMEOUT 10000 SSH connection timeout in ms
SSH_KEEPALIVE_INTERVAL 30000 SSH keepalive interval in ms
MONITOR_INTERVAL_MS 60000 Default monitoring interval in ms
SMART_INTERVAL_MS 21600000 SMART disk health check interval in ms (6 hours)
LOG_LEVEL info Winston log level
LOG_DIR ./logs Log directory
YAMOT_LDAP_ENABLED false Enable LDAP authentication
YAMOT_LDAP_URL (empty) LDAP server URL (ldap:// or ldaps://)
YAMOT_LDAP_BIND_DN (empty) Service account DN for LDAP bind
YAMOT_LDAP_BIND_PASSWORD (empty) Service account password
YAMOT_LDAP_SEARCH_BASE (empty) Base DN for user search (e.g. DC=corp,DC=local)
YAMOT_LDAP_SEARCH_FILTER (sAMAccountName={{username}}) LDAP search filter with {{username}} placeholder
YAMOT_LDAP_TLS_REJECT_UNAUTHORIZED true Reject unauthorized TLS certificates for LDAPS

Notification channels (email/SMTP, Slack, Mattermost) and LDAP settings are configured at runtime via the Settings page in the web UI or the API — not via environment variables. Each stores its own configuration in the database, overriding the env-var defaults.

Production requirements

When NODE_ENV=production, YAMOT refuses to start if JWT_SECRET or ENCRYPTION_KEY are still set to their default values. Generate random values:

# JWT secret (64 hex chars)
openssl rand -hex 32

# Encryption key (64 hex chars)
openssl rand -hex 32

CLI Usage

node src/cli/cli.js <command>

Or after npm install (creates a yamot symlink):

yamot <command>

Commands

# List configured servers
yamot servers

# Add a server (key auth)
yamot add-server -n my-server -h 192.168.1.10 -u root --key ~/.ssh/id_rsa --interval 60000

# Add a server (key auth with passphrase)
yamot add-server -n my-server -h 192.168.1.10 -u root --key ~/.ssh/id_rsa --passphrase mypassphrase --interval 60000

# Add a Proxmox VE host
yamot add-server -n pve-node1 -h 192.168.1.20 -u root --key ~/.ssh/id_rsa --type proxmox

# Add an SNMP device (community string via --community, default: public)
yamot add-server -n switch-1 -h 192.168.1.30 -u root --type snmp --community public

# Remove a server
yamot remove-server <id>

# List available monitors
yamot monitors

# List alert rules
yamot alerts

# Add an alert rule
yamot add-alert -n "High CPU" -m cpu.usagePercent -o ">" -t 90 -c email --cooldown 300000

# List users
yamot users

# Add a user
yamot add-user -u operator1 -p mypassword -r operator

# Set a user's password (or omit -p to be prompted interactively)
yamot set-password -u admin -p newpass123
yamot set-password -u admin

# Generate a report
yamot report -s <server-id> --from <timestamp-ms> --to <timestamp-ms>

# Start the web server
yamot start

Alert rule parameters

Flag Description
-n, --name Rule name
-m, --metric Metric path (e.g. cpu.usagePercent, memory.usagePercent, disk.usagePercent)
-o, --operator Comparison operator: >, <, >=, <=, ==, !=
-t, --threshold Threshold value (number)
-c, --channel Notification channel: email, slack, mattermost
--severity Alert severity: info, warning, critical (default: warning)
--cooldown Cooldown in ms before re-alerting (default: 300000 = 5 min)

Notification Channels

Alert notifications are sent through configured channels. Each channel is an independent instance (email, Slack, or Mattermost) with its own configuration stored in the database. Multiple channels of the same type can be created (e.g. two separate Slack webhooks for different teams).

Channel types

Type Configuration Notes
Email SMTP host, port, TLS, username, password, from address, recipients Password is encrypted at rest. The recipients field accepts a comma-separated list of email addresses. Falls back to the SMTP username if no recipients are set.
Slack Webhook URL Uses Slack incoming webhooks. URL must start with https://.
Mattermost Webhook URL Uses Mattermost incoming webhooks. URL must start with https://.

Channels can be created, edited, enabled/disabled, tested, and deleted from the Settings page in the web UI or via the API. A "Test" button sends a test notification to verify the configuration.

Server unreachable alerts

When all monitors for a server fail on a given tick (typically because SSH is unreachable), YAMOT automatically fires a critical alert using the built-in Server Unreachable rule. This rule appears alongside user-defined rules in the alert list and can be enabled or disabled like any other rule. When connectivity is restored, the alert is automatically resolved.

The unreachable alert has a 5-minute cooldown to avoid repeated notifications during transient network issues.

Default alert templates

When a new server is created, YAMOT automatically populates alert rules from predefined templates based on the server type. This gives every server a baseline of monitoring out of the box — no manual rule configuration needed.

Linux servers get 10 rules:

Rule Metric Operator Threshold Severity
High CPU Usage cpu.usagePercent > 90 critical
High Memory Usage memory.usagePercent > 90 critical
Disk Nearly Full disk.usagePercent > 90 critical
Disk Warning disk.usagePercent > 80 warning
High Load Average load.load1 > 5 warning
CPU Temperature High temperature.maxTemperature > 80 warning
CPU Temperature Critical temperature.maxTemperature > 90 critical
SMART Disk Failure smart.failedDisks > 0 critical
Failed SSH Logins Spike login.failedCount > 10 warning
SMART Reallocated Sectors smart.reallocatedSectors > 0 warning

Proxmox VE servers get all 10 Linux rules plus 3 PVE-specific rules (13 total):

Rule Metric Operator Threshold Severity
PVE CPU High proxmox-node.cpuUsage > 0.9 critical
PVE Cluster Not Quorate proxmox-cluster.quorate == 0 critical
PVE Node Count Mismatch proxmox-cluster.nodesOnline < 1 critical

All template rules are scoped to the specific server (scopeType: 'server') and enabled by default. Users can modify or delete individual rules as needed.

SNMP devices get 1 rule:

Rule Metric Operator Threshold Severity
SNMP Device Rebooted snmp.sysUpTime < 60000 warning

This rule fires when sysUpTime drops below 10 minutes (in hundredths of seconds), indicating a recent device reboot.

Templates can also be re-applied to existing servers from the alerts page via the Apply Templates button, which supports two modes:

  • Apply to a specific server — select a server from a dropdown
  • Apply to all servers of a type — select a server type (Linux, Proxmox VE, or SNMP) to apply templates to all servers of that type at once

Re-applying templates is idempotent: rules that already exist (same name + same server scope) are skipped, so no duplicates are created.

Web Dashboard

Route Description
/login Login page (public)
/ Dashboard — overview of all servers
/fleet Fleet dashboard — aggregated CPU, memory, disk, uptime across all servers with group filtering
/servers Server list
/servers/:id Server detail — per-monitor metrics, charts, sparklines, process table, service status, disk/network tables, custom checks
/http-checks HTTP health checks — CRUD for standalone URL monitors
/cert-checks Certificate checks — CRUD for SSL/domain expiry monitors
/groups Server group management
/alerts Active alerts + alert rule management + alert history + alert ack + maintenance windows
/reports Report generation and export
/users User management (admin only)
/settings Notification channel settings (admin only)

API endpoints

All API routes are under /api and require a JWT token (via Authorization: Bearer <token> header or token cookie).

Method Path Permission Description
POST /api/auth/login public Authenticate, returns JWT + sets cookie
POST /api/auth/logout public Clear auth cookie
GET /api/auth/verify authenticated Verify current token
GET /api/servers servers:read List all servers (credentials masked)
GET /api/servers/:id servers:read Get server details
POST /api/servers servers:write Add a server
PUT /api/servers/:id servers:write Update a server
DELETE /api/servers/:id servers:delete Remove a server
POST /api/servers/:id/toggle servers:write Enable/disable monitoring
GET /api/metrics/servers/:id/latest authenticated Latest metrics for all monitors
GET /api/metrics/servers/:id/:monitor/latest authenticated Latest metric for a specific monitor
GET /api/metrics/servers/:id/:monitor/history authenticated Metric history (query: from, to)
GET /api/metrics/servers/:id/uptime authenticated Uptime percentage for a server (query: from, to)
GET /api/metrics/fleet metrics:read Fleet-wide aggregated metrics (query: groupId)
GET /api/alerts/rules alerts:read List alert rules
POST /api/alerts/rules alerts:write Create alert rule
PUT /api/alerts/rules/:id alerts:write Update alert rule
DELETE /api/alerts/rules/:id alerts:delete Delete alert rule
GET /api/alerts/active alerts:read List active (unresolved) alerts
GET /api/alerts/history alerts:read Alert history (query: status, serverId, severity, limit, offset)
POST /api/alerts/:alertId/ack alerts:write Acknowledge an alert
POST /api/alerts/:alertId/unack alerts:write Unacknowledge an alert
GET /api/alerts/maintenance-windows alerts:read List maintenance windows
POST /api/alerts/maintenance-windows alerts:write Create a maintenance window
DELETE /api/alerts/maintenance-windows/:id alerts:write Delete a maintenance window
POST /api/alerts/templates/:serverId alerts:write Apply default alert templates to a specific server
POST /api/alerts/templates/type/:type alerts:write Apply default alert templates to all servers of a given type
GET /api/servers/:serverId/custom-checks servers:read List custom checks for a server
POST /api/servers/:serverId/custom-checks servers:write Create a custom check
PUT /api/servers/:serverId/custom-checks/:id servers:write Update a custom check
DELETE /api/servers/:serverId/custom-checks/:id servers:delete Delete a custom check
GET /api/http-checks servers:read List all HTTP checks
POST /api/http-checks servers:write Create an HTTP check
PUT /api/http-checks/:id servers:write Update an HTTP check
DELETE /api/http-checks/:id servers:delete Delete an HTTP check
POST /api/http-checks/:id/test servers:write Run an HTTP check immediately
GET /api/cert-checks servers:read List all certificate checks
POST /api/cert-checks servers:write Create a certificate check
PUT /api/cert-checks/:id servers:write Update a certificate check
DELETE /api/cert-checks/:id servers:delete Delete a certificate check
POST /api/cert-checks/:id/test servers:write Run a certificate check immediately
GET /api/users users:read List users
POST /api/users users:write Create user
PUT /api/users/:id users:write Update user
DELETE /api/users/:id users:delete Delete user
GET /api/reports/:serverId reports:read Generate report
GET /api/settings/notification-channels settings:read List notification channels
POST /api/settings/notification-channels settings:write Create notification channel
PUT /api/settings/notification-channels/:id settings:write Update notification channel
DELETE /api/settings/notification-channels/:id settings:write Delete notification channel
POST /api/settings/notification-channels/:id/test settings:write Send a test notification

Roles and permissions

Permission Admin Operator Viewer
servers:read yes yes yes
servers:write yes yes no
servers:delete yes yes no
alerts:read yes yes yes
alerts:write yes yes no
alerts:delete yes yes no
users:read yes no no
users:write yes no no
users:delete yes no no
reports:read yes yes yes
settings:read yes yes yes
settings:write yes no no

LDAP / ActiveDirectory Authentication

YAMOT supports LDAP/ActiveDirectory as an external authentication provider. When enabled, login attempts are tried against LDAP first, then fall back to the local database. The local admin account always works as a fallback.

How it works

  1. User submits username and password at the login page
  2. YAMOT connects to the LDAP server and binds with a service account (if configured)
  3. It searches for the user by the configured search filter (default: (sAMAccountName={{username}}))
  4. It rebinds as the found user's DN with the provided password to verify credentials
  5. On success: a local user record is created (if it doesn't exist yet) with the viewer role, and a JWT is issued
  6. On failure (user not found in LDAP or wrong password): falls through to local DB authentication

LDAP-provisioned users have a sentinel password (__ldap__) stored locally. This means they cannot authenticate via the local password path — they must always go through LDAP. An admin can promote an LDAP user to operator or admin via the Users page; the role is preserved across logins.

Configuration

LDAP can be configured via environment variables (see the table above) or at runtime via the Settings page in the web UI. Settings saved in the database override env-var values and take effect immediately without restarting.

Setting Description
Enable Toggle LDAP authentication on/off
Server URL ldap://host:389 or ldaps://host:636 for TLS
Bind DN Distinguished name of a service account used for the initial bind and user search (e.g. CN=yamot-svc,OU=Service Accounts,DC=corp,DC=local)
Bind Password Password for the service account (encrypted at rest)
Search Base Base DN for the user search (e.g. DC=corp,DC=local)
Search Filter LDAP filter with {{username}} placeholder. Default: (sAMAccountName={{username}}). For OpenLDAP, use (uid={{username}})
TLS reject unauthorized When unchecked, allows self-signed certificates for ldaps:// connections (not recommended for production)

The "Test Connection" button on the Settings page verifies connectivity by binding with the service account and performing a base search on the search base. It uses the values currently entered in the form, so you can test before saving.

Linking existing local users to LDAP

When an LDAP user logs in for the first time, a local record is auto-created. If a local user with the same username already exists, LDAP authentication will use that existing record (keeping its current role). However, the old local password still works — the user can authenticate via either path.

To make an existing local user LDAP-only (block local password login), an admin can set their password to the LDAP sentinel:

UPDATE users SET password = '__ldap__' WHERE username = 'jdoe';

This forces the user to authenticate through LDAP. Their role and permissions are unchanged.

Security notes

  • The bind password is encrypted at rest using AES-256-GCM (same as SSH credentials)
  • Usernames are escaped per RFC 4515 before being inserted into the search filter, preventing LDAP injection
  • Account lockout applies to LDAP users: if an LDAP user's local record is locked (e.g. by an admin), they cannot log in even with valid LDAP credentials
  • LDAP settings changes take effect on the next login — no restart needed

Monitors

Monitor Metrics collected
CPU usagePercent, userPercent, systemPercent
Memory total, available, used, usagePercent, buffers, cached, swapTotal, swapUsed, swapUsagePercent
Disk Per-filesystem: filesystem, fstype, size, used, available, usagePercent, mount
Network Per-interface: interface, rxBytes, rxPackets, rxErrors, rxDropped, txBytes, txPackets, txErrors, txDropped
Load load1, load5, load15, runningProcesses, totalProcesses
Uptime uptimeSeconds, idleSeconds, bootTime
Login recentLogins[] (user, terminal, ip, loginTime), failedAttempts[], failedCount
Processes processes[] (pid, user, cpu, mem, command) — top processes by CPU and memory
Services services[] (name, loadState, activeState, subState) — systemd service health status
Proxmox Cluster quorate, nodes, nodesOnline, expectedVotes, totalVotes — cluster quorum status (Proxmox hosts only)
Proxmox VMs vms[] (vmid, name, status, cpuCount, memoryMB), totalVms, runningVms, stoppedVms — VM inventory (Proxmox hosts only)
Proxmox Containers containers[] (ctid, name, status), totalContainers, runningContainers, stoppedContainers — LXC container inventory (Proxmox hosts only)
Proxmox Storage storagePools[] (name, type, total, used, available, usagePercent, active), totalStorageGB, usedStorageGB — storage pool usage (Proxmox hosts only)
Proxmox Node pveVersion, cpuUsage, memoryTotal, memoryUsed, memoryFree, swapTotal, swapUsed, uptime, subscriptionStatus — PVE node status (Proxmox hosts only)
SMART Per-disk: device, type, smartPassed, temperature, powerOnHours, reallocatedSectors, pendingSectors, uncorrectableSectors, model, serial, attributes[]. Aggregates: totalDisks, failedDisks, maxTemperature, reallocatedSectors, pendingSectors, uncorrectableSectors
Temperature Per-sensor: name, label, temperature. Aggregate: maxTemperature
Custom Checks Per-check: custom.<name> — output of user-defined shell commands parsed as number or JSON
SNMP Per-OID: configurable labels (default: sysUpTime, sysDescr). Polled via SNMPv2c UDP, no SSH required
HTTP Checks Per-check: http-check.<name>.statusCode, http-check.<name>.responseTimeMs, http-check.<name>.success. Runs from YAMOT host, no SSH required
Certificate Checks Per-check: cert-check.sslDaysLeft, cert-check.domainDaysLeft, cert-check.success. SSL expiry via TLS, domain expiry via WHOIS. Runs from YAMOT host, no SSH required

Metrics are collected every intervalMs (per-server, default 60s) and stored in SQLite. A daily cleanup job removes metrics older than 30 days.

SMART disk health

The SMART monitor retrieves disk health data from physical disks using smartctl (from the smartmontools package). It runs at a lower frequency than other monitors — by default every 6 hours (SMART_INTERVAL_MS) — because SMART data changes slowly and smartctl can be slow on some drives. The monitor self-throttles on every scheduler tick, only executing SSH commands when the interval has elapsed.

Requirement: smartmontools must be installed on each monitored server. On Debian/Ubuntu: apt install smartmontools. On RHEL/CentOS: dnf install smartmontools. The SSH user must have read access to disk devices — typically this means connecting as root, or configuring sudo rules that allow the SSH user to run smartctl without a password.

The monitor discovers disks via smartctl --scan, then queries each disk with smartctl -j -a (JSON output) in a single SSH round-trip. It supports both ATA/SATA and NVMe drives. If smartctl is not installed on a server, the monitor silently fails and the SMART section does not appear on that server's detail page.

Alert rules can target SMART metrics: smart.failedDisks (alert when > 0), smart.maxTemperature, smart.reallocatedSectors, smart.pendingSectors, smart.uncorrectableSectors.

CPU temperature

The Temperature monitor reads CPU and system temperatures from two Linux kernel interfaces via SSH:

  1. /sys/class/hwmon/ — the hardware monitoring interface (coretemp, k10temp, acpitz, etc.). Present on most x86 systems.
  2. /sys/class/thermal/thermal_zone*/ — the kernel thermal framework interface. Available since kernel 2.6.x on all Linux distributions; often the only source on ARM boards and some VMs.

Both are kernel-provided sysfs interfaces — no additional packages need to be installed on target servers. Works on Ubuntu, Fedora, CentOS, openSUSE, Debian, and other Linux distributions. The monitor runs on every tick (default 60s) since reading sysfs is instant.

Each sensor is reported with its source name (e.g. coretemp, k10temp, thermal_zone), optional label (e.g. Package id 0, Core 0, x86_pkg_temp), and temperature in degrees Celsius. The maxTemperature aggregate is the highest reading across all sensors.

Alert rules can target temperature.maxTemperature to trigger when CPU temperature exceeds a threshold.

Service health checks

The Services monitor checks the status of systemd services on the remote server. By default, it reports on common services (ssh, cron, systemd-journald). To watch specific services, add a watchServices array to the server configuration:

yamot add-server -n my-server -h 192.168.1.10 -u root --key ~/.ssh/id_rsa --watchServices nginx,postgresql,redis

Service names are validated against a whitelist of allowed characters (alphanumeric, hyphen, underscore, dot).

Proxmox VE monitoring

YAMOT supports Proxmox VE hosts alongside standard Linux servers. When a server is declared with type: 'proxmox', five additional monitors run automatically alongside the standard Linux monitors (Proxmox is Debian-based, so CPU, memory, disk, and network monitors work normally).

All Proxmox data is collected via SSH using Proxmox CLI tools — no API tokens or REST API calls are required.

Monitor Command Metrics
proxmox-cluster pvecm status Cluster quorum, node counts, vote counts
proxmox-vms qm list VM inventory (VMID, name, status, CPU, memory)
proxmox-containers pct list LXC container inventory (CTID, name, status)
proxmox-storage pvesm status Storage pool usage (total, used, available, usage %)
proxmox-node pvesh get /nodes/$(hostname)/status --output-format json Node CPU, memory, swap, uptime, PVE version, subscription

To add a Proxmox host via CLI:

yamot add-server -n pve-node1 -h 192.168.1.20 -u root --key ~/.ssh/id_rsa --type proxmox

Or via the web UI, select "Proxmox VE" from the Server Type dropdown in the Add Server modal.

Proxmox-specific data is displayed on the server detail page: cluster status, VM inventory table, container inventory table, storage pool usage with usage bars, and PVE node information (version, subscription status). On the dashboard, Proxmox servers show a "PVE" badge on their server card.

SNMP device monitoring

YAMOT supports SNMP v2c devices (switches, routers, UPS units, printers) alongside SSH-based servers. When a server is declared with type: 'snmp', only the SNMP monitor runs — SSH-based monitors are excluded.

SNMP polling uses a built-in BER (Basic Encoding Rules) encoder/decoder over raw UDP datagrams — no external SNMP library dependency. The device's community string, port, and OIDs are stored in the server's snmpConfig.

Default OIDs polled if none are configured:

Label OID Description
sysUpTime 1.3.6.1.2.1.1.3.0 System uptime in hundredths of seconds
sysDescr 1.3.6.1.2.1.1.1.0 System description string

To add an SNMP device via the web UI, select "SNMP Device" from the Server Type dropdown in the Add Server modal and enter the community string. SSH key fields are hidden for SNMP devices.

Collected SNMP values are displayed on the server detail page in a metrics table. Alert rules can target snmp.<label> (e.g. snmp.sysUpTime).

Custom check scripts

Custom checks let you run arbitrary shell commands on remote servers via SSH and use the output as metrics. Each check has a name, a command, and a format (number or json). The output is trimmed and parsed accordingly:

  • number — parsed with parseFloat(); non-numeric output becomes null
  • json — parsed with JSON.parse(); invalid JSON becomes null

Metrics are exposed as custom.<checkName> and can be targeted by alert rules. Custom checks are managed per-server from the server detail page in the web UI or via the API.

HTTP health checks

HTTP checks are standalone monitors that run from the YAMOT host — no SSH or SNMP required. Each check polls a URL at its own interval and records:

  • Status code — compared against an expected status (default 200)
  • Response time — measured in milliseconds
  • Body match — optional regex or substring match against the response body
  • Success1 if status code matches and body match passes (if configured), 0 otherwise

HTTP checks run on a separate scheduler from the main SSH-based monitors. Results are published on the event bus with a synthetic serverId of http-check:<id>, so alert rules can target http-check.<name>.statusCode, http-check.<name>.responseTimeMs, or http-check.<name>.success.

HTTP checks are managed from the dedicated HTTP Checks page in the web UI or via the API. A "Test" button runs a check immediately to verify the configuration.

SSL certificate and domain expiration checks

Certificate checks are standalone monitors that run from the YAMOT host — no SSH or SNMP required. Each check monitors two things independently:

  • SSL/TLS certificate expiry — connects to the domain via TLS (tls.connect) and reads the certificate's valid_to date, computing days until expiry. Set port to 0 to skip SSL entirely (domain-only monitoring).
  • Domain name expiry — queries the authoritative WHOIS server via raw TCP (port 43) and parses the expiry date from the response. Supports 40+ TLD-specific WHOIS servers with fallback to whois.iana.org. Uncheck the WHOIS option to skip domain expiry (SSL-only monitoring).

Both checks have a 10-second timeout. The default interval is once per day (86400000 ms) but can be configured per check.

Metrics are published on the event bus with a synthetic serverId of cert-check:<id>, so alert rules can target:

  • cert-check.sslDaysLeft — days until SSL certificate expires (-1 if check failed or skipped)
  • cert-check.domainDaysLeft — days until domain registration expires (-1 if check failed or skipped)
  • cert-check.success1 if at least one check (SSL or WHOIS) produced a result

Recommended alert rules:

Rule Metric Operator Threshold Severity
SSL expiring soon cert-check.sslDaysLeft < 30 warning
SSL expired cert-check.sslDaysLeft < 0 critical
Domain expiring soon cert-check.domainDaysLeft < 30 warning
Domain expired cert-check.domainDaysLeft < 0 critical

Scope: "All Servers" (matches all cert checks via the synthetic serverId).

Certificate checks are managed from the dedicated Certificates page in the web UI or via the API. A "Test" button runs a check immediately and displays the days remaining for both SSL and domain.

Architecture

yamot/
├── src/
│   ├── index.js                 # Application entry point
│   ├── config/index.js          # Environment-based configuration
│   ├── cli/cli.js               # CLI (commander-based)
│   ├── core/
│   │   ├── event-bus.js         # Pub/sub event bus (class, injected via constructors)
│   │   ├── scheduler.js          # Per-server monitoring scheduler
│   │   ├── http-check-scheduler.js # Standalone HTTP check scheduler
│   │   ├── cert-check-scheduler.js # Standalone SSL/domain expiry check scheduler
│   │   ├── snmp/snmp-client.js  # SNMP v2c BER encode/decode + UDP client
│   │   └── ssh/
│   │       ├── connection-manager.js  # SSH connection pool with host key verification (TOFU)
│   │       └── executor.js      # Remote command execution
│   ├── monitors/                # 20 monitors + registry (11 standard + 5 Proxmox + custom checks + SNMP + HTTP checks + cert checks)
│   ├── alerts/
│   │   ├── alert-manager.js     # Evaluates rules on metric events + unreachable detection + maintenance windows
│   │   ├── alert-rule.js        # Threshold comparison logic
│   │   ├── alert-templates.js   # Default alert rule templates per server type
│   │   └── channels/            # email, slack, mattermost (WebhookChannel base)
│   ├── storage/
│   │   ├── database.js          # SQLite init + migrations
│   │   ├── migrations/          # Versioned schema migrations
│   │   └── repositories/        # server, metric, alert, user, uptime, notification-channel, known-host-key, maintenance-window, custom-check, http-check, cert-check CRUD
│   ├── auth/
│   │   ├── auth-service.js      # JWT login/verify
│   │   ├── rbac.js              # Permission middleware
│   │   └── roles.js             # Role definitions
│   ├── reports/                 # Report generation
│   ├── web/
│   │   ├── server.js            # Express + Handlebars + Socket.io
│   │   ├── routes/              # API and page routes
│   │   ├── middleware/          # Auth, error handling
│   │   ├── socket/              # Socket.io manager (JWT-authenticated)
│   │   └── views/               # Handlebars templates
│   └── utils/
│       ├── helpers.js           # Formatting + password hashing (scrypt)
│       ├── crypto.js            # AES-256-GCM encrypt/decrypt
│       ├── validator.js         # Input validation
│       └── logger.js            # Winston logger
├── public/
│   ├── less/                    # LESS source (variables, mixins, components)
│   ├── js/                      # Client-side JS (charts, dashboard, socket)
│   └── vendor/                  # Vendored libraries (Font Awesome, Chart.js)
├── scripts/compile-less.js      # LESS → CSS compiler
├── test/                        # Node.js test runner (291 tests)
└── .env.example

Key design decisions

  • SQLite (via better-sqlite3) — zero-dependency deployment, single file
  • Event bus — decouples monitors from storage and alerts. A single instance is created at startup and injected via constructors (not a module-level singleton)
  • Dependency injection — all repositories and services receive their dependencies via constructors. No service locator fallback
  • SSH connection pool — persistent connections reused across monitor runs with host key verification (TOFU)
  • Scrypt with per-user salt for password hashing
  • AES-256-GCM encryption for SSH private keys, passphrases, and SMTP passwords at rest
  • CSRF protection — double-submit cookie pattern for state-changing API requests
  • JWT revocation — token version mechanism invalidates tokens on password/role changes
  • LESS — variables and mixins at root, self-contained component files, compiled to single CSS

Network traffic estimates

Each SSH-based server runs 13-14 monitors per tick (default 60s). Each monitor opens a separate SSH channel on a persistent pooled connection (no re-authentication per tick). The table below shows the estimated traffic per monitor per tick:

Monitor Command size Output size SSH overhead Total
cpu 50 B 100 B 130 B ~280 B
memory 20 B 2 000 B 130 B ~2 150 B
disk 100 B 500 B 130 B ~730 B
network 20 B 300 B 130 B ~450 B
login 300 B 2 500 B 130 B ~2 930 B
load 20 B 20 B 130 B ~170 B
uptime 40 B 50 B 130 B ~220 B
process 80 B 3 500 B 130 B ~3 710 B
service 100 B 50 B 130 B ~280 B
temperature 500 B 200 B 130 B ~830 B
file-integrity 400 B 500 B 130 B ~1 030 B
suid-files 300 B 1 500 B 130 B ~1 930 B
listening-ports 400 B 500 B 130 B ~1 030 B
Per tick, per server ~15 700 B

SSH overhead (~130 B/exec) covers channel open/close, cipher padding, and TCP ACKs. SMART runs every 6 hours (~5-10 KB per run) and is negligible when amortized.

10 SSH servers, 60s interval

Period Estimated traffic
Per minute ~157 KB
Per hour ~9.4 MB
Per day ~226 MB
Per month ~6.7 GB

SSH keepalive packets (~100 B every 30s per server) add ~5.7 MB/day for 10 servers. The initial SSH key exchange at startup is ~5-10 KB per server (one-time cost).

Reducing traffic

  • Disable unneeded monitorssuid-files, file-integrity, and listening-ports are the most verbose and rarely critical
  • Increase the interval — doubling to 120s halves the traffic
  • Enable SSH compression — can reduce text output sizes by 50-70%

FAQ

Does each monitor open a new SSH connection?

No. YAMOT maintains one persistent SSH connection per server, reused across all monitors and all ticks. On first use, the connection is established (TCP handshake, key exchange, authentication). On every subsequent monitor run, the existing connection is reused -- each monitor opens a new SSH channel on the same TCP socket (channels are multiplexed, no re-authentication).

Server tick (every 60s)
  └─ Monitor cpu        → executor.exec(server, cmd) → channel on existing connection
  └─ Monitor memory      → executor.exec(server, cmd) → channel on existing connection
  └─ Monitor disk        → executor.exec(server, cmd) → channel on existing connection
  └─ ... (13 monitors, 13 channels, 1 TCP connection)

The connection stays open between ticks. Keepalive packets are sent every 30s to prevent idle disconnection. If the connection drops, the next monitor run triggers an automatic reconnect.

Why do I see "Command timeout" errors in the logs?

The default command timeout is 60s per SSH command. Timeouts typically occur when:

  • The remote server is under heavy load (e.g. suid-files runs a full find / -xdev scan)
  • The network between YAMOT and the target server is slow or saturated
  • The SSH server has a low MaxSessions limit and channels are exhausted

If timeouts are frequent, consider disabling expensive monitors (suid-files, file-integrity) or increasing the monitoring interval.

Why do I see "previous tick still running" warnings?

This means the total time for all 13+ monitors on a server exceeded the tick interval (default 60s). Monitors run sequentially within a tick. The cpu monitor sleeps 1 second (to sample /proc/stat twice), and suid-files can take 10-30s on large filesystems. If the combined time exceeds 60s, the next tick is skipped.

Solutions: disable expensive monitors, increase the per-server interval, or reduce the number of monitored servers on a single YAMOT instance.

Development

# Install dependencies
npm install

# Compile LESS (one-shot)
npm run less

# Compile LESS in watch mode during development
npm run less:watch

# Start in development mode (auto-restart on file changes)
npm run dev

# Run tests
npm test

# Start with a custom DB path
YAMOT_PORT=3001 DB_PATH=/tmp/yamot-dev.db npm start

Testing

npm test

Tests use the Node.js built-in test runner (node --test) and cover:

  • Monitor parsing (CPU, memory, disk, network, load, uptime, processes, services, SMART, temperature)
  • Proxmox monitor parsing (cluster status, VM inventory, container inventory, storage pools, node status)
  • SNMP BER encode/decode (integer, OID, octet string, PDU construction, PDU parsing, round-trip, edge cases)
  • SNMP monitor (default OIDs, configured OIDs, error handling, multiple OIDs, port configuration)
  • Custom check monitor (number/JSON parsing, repository injection, per-server checks)
  • HTTP check monitor (status code, response time, body match, timeout)
  • Certificate check monitor (SSL expiry via TLS, domain expiry via WHOIS, buildMetrics, WHOIS parsing, TLD routing)
  • Alert rule evaluation (all operators, missing metrics, scoping, severity levels)
  • Default alert templates (Linux, Proxmox, and SNMP definitions, auto-population, duplicate prevention, server scoping)
  • Alert acknowledgment (ack/unack, maintenance window suppression)
  • Server unreachable alert (trigger, dedup, resolve, no-op on recovery)
  • Fleet API endpoint (aggregation, group filtering, alert counts)
  • RBAC role permissions
  • Password hashing and verification (scrypt)
  • Server repository CRUD + credential encryption at rest
  • Server group repository CRUD + membership management
  • Notification channel repository CRUD + SMTP password encryption
  • Uptime repository (uptime percentage tracking)
  • Crypto utility (round-trip, tamper detection, null handling)

License

PolyForm Noncommercial License 1.0.0 — see LICENSE for the full text.

Free for non-commercial use. Commercial use requires a separate license from the copyright holder.

S
Description
No description provided
Readme
2.3 MiB
Languages
JavaScript 82.7%
Handlebars 10.5%
Less 6.8%