Skip to content

Repository files navigation

HumWatch

"What hums beneath the shell."

A self-hosted, local-first hardware monitoring system for Windows and Linux PCs.


Features

  • Full hardware telemetry — CPU, GPU, memory, disk, network, fans, battery
  • 7-day rolling history in a local SQLite database
  • Real-time dashboard with live gauges, charts, and sparklines
  • Server-Sent Events for instant updates without polling
  • Multi-machine view — monitor all your PCs from one dashboard
  • Zero-config discovery — GPU and battery sections appear automatically when hardware is present
  • Themeable — ships with three themes, easy to create your own
  • Local-first — no cloud, no accounts, all data stays on your machine
  • Network-agnostic — works over LAN, Tailscale, ZeroTier, WireGuard, or any overlay network

What It Monitors

Category Metrics
CPU Per-core temperature, load, clock speed, package power, voltage
GPU Temperature, load, VRAM, core/memory clocks, power draw, fan speed
Memory RAM and swap usage
Disk Read/write throughput, volume usage, drive temperatures
Network Upload/download rates, cumulative transfer
Battery Charge level, charge rate, voltage, health/wear, cycle count
Fans Individual fan RPMs
Processes Top processes by CPU and memory usage

Requirements

  • Windows 10/11 or Linux (any distro with /sys/class/hwmon)
  • A web browser for the dashboard
  • Overlay network (optional) — Tailscale, ZeroTier, WireGuard, or similar for multi-machine access across networks

Installation

Installer (recommended)

  1. Download HumWatch-Setup-vX.X.X.exe from the releases page
  2. Run the installer — click Yes when Windows asks for admin access
  3. Follow the prompts (default install path C:\HumWatch is fine)

The installer handles everything except one optional driver: bundled Python 3.12, all dependencies, LibreHardwareMonitor v0.9.6, Windows service registration, firewall rule, and auto-start on boot. For temperatures, fans, voltages, and GPU metrics, also install the PawnIO driver once: winget install PawnIO.PawnIO. Without it HumWatch runs in psutil-only mode (CPU load, memory, disk, network, battery).

Once installed, open https://localhost:9100 in your browser. The first visit may require certificate trust setup... trust the HumWatch certificate or its issuing local CA in the operating system and browser certificate stores.

From source (development)

cd HumWatch
./setup.sh

# (Optional) Download LibreHardwareMonitor for full sensor access
# Also installs the PawnIO driver (replaces deprecated WinRing0)
powershell -ExecutionPolicy Bypass -File scripts\download-lhm.ps1

./run.sh

run.sh refuses to start with no token or certificate configured, since HumWatch now requires TLS and a bearer token even for development. On an unprivileged source checkout it auto-provisions a local development profile under .humwatch/, a random bearer token, a self-signed localhost certificate, and a user-writable data root, then prints the token to paste into the dashboard's token prompt at https://localhost:9100.

Running python -m agent.main directly skips that provisioning, so it requires HUMWATCH_AUTH_TOKEN_FILE (or HUMWATCH_AUTH_TOKEN), HUMWATCH_TLS_CERTFILE, and HUMWATCH_TLS_KEYFILE to already be set, plus HUMWATCH_DATA_DIR if the default runtime root is not writable.

Without LibreHardwareMonitor, HumWatch runs in psutil-only mode — you get CPU load, memory, disk, network, and battery basics. With LHM, you also get temperatures, voltages, GPU metrics, fan speeds, and more.

Note: LHM v0.9.5+ requires the PawnIO driver (replaces the deprecated WinRing0 driver). The download-lhm.ps1 script installs it via winget on development machines. The Windows installer does not install drivers, so run the winget command once on installed machines that need full sensors.

Install as a Windows Service (from source)

# Run as Administrator
powershell -ExecutionPolicy Bypass -File scripts\install-service.ps1

This registers HumWatch as an auto-start service with log rotation. To remove:

powershell -ExecutionPolicy Bypass -File scripts\uninstall-service.ps1

Linux (from source)

On Linux, HumWatch reads sensors directly from /sys/class/hwmon, /sys/class/drm, and /proc/cpuinfo... no LHM, no extra drivers.

cd HumWatch
./setup.sh
./run.sh

run.sh refuses to start with no token or certificate configured, since HumWatch now requires TLS and a bearer token even for development. On an unprivileged source checkout it auto-provisions a local development profile under .humwatch/, a random bearer token, a self-signed localhost certificate, and a user-writable data root, then prints the token to paste into the dashboard's token prompt at https://localhost:9100.

Running python -m agent.main directly skips that provisioning, so it requires HUMWATCH_AUTH_TOKEN_FILE (or HUMWATCH_AUTH_TOKEN), HUMWATCH_TLS_CERTFILE, and HUMWATCH_TLS_KEYFILE to already be set, plus HUMWATCH_DATA_DIR if the default runtime root is not writable.

To install as a systemd service, first provision the humwatch user, /etc/humwatch, and the runtime credentials the unit expects:

sudo scripts/provision-security.sh --app-dir /opt/HumWatch

Then edit WorkingDirectory and ExecStart in humwatch.service to match where you cloned it (they default to /opt/HumWatch), and install the unit:

sudo cp humwatch.service /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now humwatch

Check status with systemctl status humwatch and logs with journalctl -u humwatch -f.

Security Hardening

HumWatch v2 treats an install as a security boundary, not a file copy. The highlights, all on by default:

  • Bearer token auth and TLS everywhere. Every listener requires HTTPS and every protected endpoint requires a bearer token. Install-time provisioning generates the certificate and token rather than leaving them as homework.
  • Locked-down install ACLs on Windows. The runtime data under C:\ProgramData\HumWatch (token, TLS material, database, logs) is stripped to SYSTEM and Administrators only. The application tree grants ordinary users read and execute, nothing more. Both routines resolve their target first and refuse to harden a volume root, so a scripted /DIR="C:\" aborts instead of rewriting ACLs across the whole drive.
  • Owner-only private keys. The TLS private key is created with a restricted mode from the first byte, never at the default umask.
  • A sandboxed Linux service. The systemd unit runs as the dedicated humwatch user with NoNewPrivileges, ProtectSystem=strict, ProtectHome, PrivateTmp, RestrictSUIDSGID, LockPersonality, and UMask=027. ReadWritePaths is scoped to /var/lib/humwatch and /var/log/humwatch, so the service cannot write to its own config directory, let alone the rest of the filesystem.
  • A locked-down browser surface. Responses carry a default-src 'self' Content Security Policy with no external script, style, font, or connect sources, plus X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, X-Frame-Options: DENY, a Permissions-Policy that blocks camera, microphone, geolocation, USB, and payment, Cache-Control: no-store on protected API paths, and HSTS over HTTPS. Every browser asset (Chart.js, Lucide, both fonts) is vendored and served same-origin, so the CSP has nothing to make an exception for.
  • Brute force costs something. Five failed bearer tokens from one source inside 60 seconds and that source gets 429 with Retry-After: 60 until the window clears. The tracking table is bounded at 1024 sources with LRU eviction, so the throttle cannot itself be used to exhaust memory.
  • Bounded requests. Request bodies are capped at 1 MiB (413 over the line), with hard ceilings on metrics per request, concurrent SSE subscribers, history window, and sample resolution. Nothing an unauthorized caller sends can make the agent allocate without limit.
  • A pinned supply chain. Everything the Windows build downloads comes from a single asset manifest with pinned URLs and SHA-256 digests, verified before use. No floating URLs, no unverified fetches.
  • Honest exit codes. A failed Windows install now exits nonzero instead of reporting success, so silent and scripted installs can trust the result.
  • Upgrades keep your config. On Debian, config.json is a dpkg conffile, so package upgrades preserve operator edits instead of overwriting them.

Dashboard

The dashboard sidebar contains these pages:

Page Description
Overview CPU load, GPU load, RAM usage, temps, battery at a glance
CPU Per-core load, temperatures, clock speeds, power draw
GPU Temperature, load, VRAM, fan speed (only with a dedicated GPU)
Memory RAM and swap usage over time, top memory consumers
Disk Read/write speeds, volume usage, drive temperatures
Network Upload/download rates, cumulative transfer
Battery Charge level, charge rate, health/wear (laptops only)
Processes Top 10 resource-consuming processes, updated live
Machines Multi-machine view with status cards
Settings Themes, alert thresholds, configuration

The connection indicator in the sidebar shows: green = live, amber = reconnecting, gray = disconnected.

Multi-Machine Setup

Each PC runs its own HumWatch instance. For remote access, prefer a Tailscale name or address and use an HTTPS peer URL such as https://machine-name.tailnet.example:9100. If Tailscale is unavailable, use the LAN hostname or address only after the certificate SAN covers that name or address and the issuing CA is trusted by the client. Never make a plaintext HTTP URL the remote default.

  1. Ensure machines can reach each other over Tailscale first, or over the LAN as the certificate fallback
  2. Install HumWatch on each machine you want to monitor
  3. Distribute the same bearer token file to each trusted peer through an operator controlled secure channel. Do not email it, commit it, or put it in a URL.
  4. Trust the issuing CA or certificate on every client machine, then add the HTTPS peer URL in Machines
  5. On each machine, set cors_origins (or HUMWATCH_CORS_ORIGINS) to the dashboard origins of the other machines that should be able to view it, for example https://machine-a.tailnet.example:9100. cors_origins defaults to an empty list, which blocks every cross-origin browser request, so the Machines page cannot fetch a peer until that peer lists your dashboard's origin. Wildcards are rejected outright, the server refuses to start with a * entry.
  6. Click any machine card to open its full dashboard

IPs are saved per-browser. HumWatch auto-discovers other instances on your local subnet and via Tailscale/ZeroTier CLI if installed. Discovery probes peers over HTTPS with certificate verification, so a peer using the self-signed certificate that provisioning generates for it is not discovered unless trusted_ca_file (or HUMWATCH_TRUSTED_CA_FILE) points at a CA that issued it. This failure mode is silent, an untrusted peer just never shows up, nothing errors.

Managing the Service

Action Command
Check status nssm status HumWatch
Stop nssm stop HumWatch
Start nssm start HumWatch
Restart nssm restart HumWatch

Run these from an Administrator PowerShell. NSSM lives at C:\HumWatch\tools\nssm.exe.

Uninstalling

Use Windows Settings > Apps (or Control Panel > Programs and Features) to uninstall HumWatch. The uninstaller stops and removes the service and immutable application files. Telemetry in C:\ProgramData\HumWatch or /var/lib/humwatch, shared token files, certificates, private keys, and other secret material are preserved unless the operator explicitly chooses destructive cleanup after explicit operator confirmation and after exporting anything that must be retained. No telemetry or secret material is silently deleted.

Configuration

HumWatch reads from config.json at the install root. Environment variables take precedence.

Setting Config Key Env Var Default
Port port HUMWATCH_PORT 9100
Bind address host HUMWATCH_HOST 0.0.0.0
Collection interval collection_interval_seconds HUMWATCH_INTERVAL 10
Data retention retention_days 7
Database path db_path HUMWATCH_DB humwatch.db
Runtime data root data_root HUMWATCH_DATA_DIR /var/lib/humwatch or C:\ProgramData\HumWatch
API token file auth_token_file HUMWATCH_AUTH_TOKEN_FILE none
TLS certificate tls_certfile HUMWATCH_TLS_CERTFILE none
TLS private key tls_keyfile HUMWATCH_TLS_KEYFILE none
Trusted CA file trusted_ca_file HUMWATCH_TRUSTED_CA_FILE system trust
Local plaintext exception allow_insecure_localhost HUMWATCH_ALLOW_INSECURE_LOCALHOST false
Cross-origin dashboard allowlist cors_origins HUMWATCH_CORS_ORIGINS (comma separated) []

By default HumWatch listens on all interfaces and requires HTTPS, so the dashboard is reachable at https://localhost:9100 and at the machine's LAN or Tailscale address after the certificate is trusted. Set host to 127.0.0.1 and allow_insecure_localhost to true only for explicit local development. That exception still requires the bearer token and must not be used for LAN, Tailscale, or other remote access.

Alert thresholds (CPU/GPU temp, RAM/disk usage, battery level) can be set in config.json or overridden per-browser in the Settings page.

After editing config.json, restart the service: nssm restart HumWatch

API security setup

Remote and non loopback listeners require a certificate, private key, and bearer token. Keep the token outside the repository in a protected file. A minimal config.json security section is:

{
  "data_root": "/var/lib/humwatch",
  "db_path": "/var/lib/humwatch/humwatch.db",
  "auth_token_file": "/etc/humwatch/token",
  "tls_certfile": "/etc/humwatch/humwatch.crt",
  "tls_keyfile": "/etc/humwatch/humwatch.key",
  "trusted_ca_file": "/etc/humwatch/humwatch-ca.crt"
}

Create the token without printing it, then restrict the file to the service account. On Linux:

sudo install -d -m 700 /etc/humwatch /var/lib/humwatch
sudo .venv/bin/python -c "import secrets; from pathlib import Path; Path('/etc/humwatch/token').write_text(secrets.token_urlsafe(32), encoding='utf-8')"
sudo chmod 600 /etc/humwatch/token

HumWatch retains the 32 byte minimum and rejects low diversity values. The shared token quality predicate requires at least 8 distinct characters, a Shannon entropy estimate of at least 2.5 bits per character, and no character to account for more than 75 percent of the token. It accepts normal cryptographically generated URL safe tokens and 64 character hexadecimal tokens. These checks are a deployment quality gate, not a substitute for generating the token with a cryptographic random source.

On Windows, use C:\ProgramData\HumWatch\token, with the certificate and private key in the same protected directory. Grant read access only to the HumWatch service account and administrators. Do not put the token in a URL, browser history, logs, source control, or a shared config export. HUMWATCH_AUTH_TOKEN is supported for development and process managers, but a protected token file is the deployment default.

For local development only, set host to 127.0.0.1, set allow_insecure_localhost to true, and provide a high quality token through HUMWATCH_AUTH_TOKEN. This explicit override allows plaintext only on loopback. It does not make LAN or overlay access safe.

To rotate the shared token, stop HumWatch, replace the token file with a newly generated value, update every trusted client, and restart HumWatch. The old value stops working as soon as the new file is loaded. Never paste either value into a command or report.

The certificate must contain the hostname or IP used by the client in its SAN. Install the issuing CA, or the self signed certificate when appropriate, in the browser and operating system trust store. For HumWatch peer clients, set trusted_ca_file or HUMWATCH_TRUSTED_CA_FILE to that CA bundle. A client must fail closed on an untrusted certificate and must not disable TLS verification.

The shared token file and the certificate trust chain are deployment material, not release files. Copy them to each approved peer through a protected operator channel, then configure the peer to read its local protected paths. A LAN certificate is the fallback when Tailscale cannot carry the connection. The remote default remains HTTPS in both cases.

Theming

HumWatch ships with three themes:

Theme Description
Default Void black with gold and teal accents
Light Warm off-white with blue accents
Terminal Green-on-black, monospace, scanlines

Switch themes from Settings in the dashboard.

To create your own theme, see THEMING.md.

Troubleshooting

Can't open localhost:9100 — Check that the service is running: nssm status HumWatch. If stopped, run nssm start HumWatch. Open https://localhost:9100 after trusting the certificate. If port 9100 is taken, change port in config.json and restart the service.

No temperatures or fan speeds — Restart the service. Some hardware isn't supported by LibreHardwareMonitor. Check C:\HumWatch\logs\humwatch-stderr.log for errors.

GPU section missing — Normal if you only have integrated graphics. LibreHardwareMonitor can't read sensors on some integrated GPUs.

Battery section missing — Normal on desktops.

Can't reach Machine A from Machine B — Verify both machines are on the same network (LAN or overlay), HumWatch is running on Machine A, and you can ping Machine A's IP. If the firewall rule is missing:

netsh advfirewall firewall add rule name="HumWatch" dir=in action=allow protocol=TCP localport=9100

Dashboard says "Disconnected" — Usually resolves in seconds. If not, check that the service is running and hard-refresh the browser (Ctrl+Shift+R).

Database growing large — Reduce retention_days in config.json and restart the service.

API

HumWatch exposes a versioned REST API on the same HTTPS listener as the dashboard. /api/v1 is the client contract. /api remains a compatibility alias for the current version during this transition.

Endpoint Description
GET /api/v1/health Unauthenticated status, version, API version, capabilities, auth and TLS requirements
GET /api/v1/info Authenticated machine identity
GET /api/v1/config Authenticated configuration and thresholds
GET /api/v1/current Authenticated latest readings grouped by category
GET /api/v1/history?metric=cpu_temp_package&from=...&to=... Authenticated historical time series (auto downsampled)
GET /api/v1/history/multi?metrics=cpu_temp_package,gpu_temp&from=...&to=... Authenticated multiple metrics in one request
GET /api/v1/processes Authenticated current top processes
GET /api/v1/sse Authenticated Server Sent Events stream

Protected requests must send exactly one header in this form:

Authorization: Bearer <token>

Missing, malformed, or incorrect credentials receive 401 Unauthorized with WWW-Authenticate: Bearer. Tokens are never accepted in query parameters, paths, cookies, or request bodies. Health is intentionally public so peer discovery can identify a HumWatch instance before making its authenticated /api/v1/info request.

The SlipShell client should keep the base URL, bearer token, and CA trust bundle behind an injectable client seam. Its request sequence is: call /api/v1/health without credentials, require api_version: 1 and the expected capabilities, then call protected endpoints with the bearer header over HTTPS. The client should surface 401 as an authentication or rotation event and surface certificate failures as trust configuration errors. It must never put the token in a URL or disable certificate verification.

Upgrades preserve the configured token and certificate paths. Clients should pin /api/v1, inspect the health capability fields, and fail closed when the reported API version is unsupported. Breaking changes require a new API version. The unversioned /api alias is a compatibility aid, not the seam for new client work. Older peers may continue using the compatibility alias during an upgrade, but they still require the shared token and trusted certificate. Upgrade the peer application first, retain its protected runtime paths, then switch the client to /api/v1 after its health response reports version 1.

All timestamps are ISO 8601 UTC. History queries are automatically downsampled based on the requested time range.

Tech Stack

  • Agent: Python 3.10+, FastAPI, Uvicorn, psutil, aiosqlite
  • Sensors: LibreHardwareMonitor on Windows (optional, via pythonnet); /sys/class/hwmon + /sys/class/drm on Linux
  • Database: SQLite in WAL mode
  • Dashboard: Vanilla JavaScript, Chart.js 4, Lucide Icons (vendored, served same-origin)
  • Fonts: Inter, JetBrains Mono (vendored, served same-origin)

Contributing

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test with python -m agent.main
  5. Submit a pull request

License

GPL-3.0 — Copyright (c) 2026 Static Hum Studio


A Static Hum Studio Production

About

Self-hosted hardware monitoring for Windows PCs

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages