Commit Graph
34 Commits
Author SHA1 Message Date
ARIA b8e91e3c91 fix: degrade gracefully when pyserial is missing
A stale venv after a code update (editable install + git pull) left
pyserial out of the environment, and the hard module-level import in
wireview.py took the entire web server down — even on machines without
a WireView device.

Guard the import: without pyserial the serial transport is disabled
(one-time warning, connect fails, reads return None) while the rest of
the server keeps running. The hwmon transport is unaffected.

Also simplify the except clauses to OSError (SerialException is an
OSError subclass) so they no longer reference the possibly-None module.
2026-10-03 13:32:43 +02:00
ARIA 526b71b45f feat: native WireView Pro II monitoring (detection, serial protocol, live tab)
Standalone support for the ThermalGrizzly WireView Pro II without
depending on the external wireview_reporter exporter:

- wireview.py: serial protocol (STX/ETX + 16-bit CRC16-CCITT) with
  vendor-data product identification, config version, UID, build
  string, screen layout, and temperature/power/current sensor reads;
  hwmon fallback with per-channel index resolution; udev-based
  detection (vendor 0x2560 / product 0x0101) with fallback port
  probing; serial read timeout and watchdog reconnect
- server.py: startup detection (root only), 1 Hz poller gated on
  subscribed clients, WS 'wireview' channel, GET /api/wireview,
  rejected-product memoization, stale-read race guard
- frontend: WireView tab (visible when a device is detected) with
  live temperature/power/current cards, sparkline history, and
  device info; nullable temp channels
- tests: 76 tests covering parser, CRC, fault classification,
  hwmon resolution, JSON safety, and the serial transport against
  a pty-based fake device

Verified live: tab appears with the device connected, disappears
when unplugged, reconnects on re-plug, no serial traffic when idle.
2026-10-03 13:20:56 +02:00
Pakobbix cc102f26c1 Merge pull request 'feat: experimental NVIDIA power control via RM ioctl interface' (#10) from feat/rm-power-control into main
Reviewed-on: #10
2026-09-17 20:48:46 +00:00
ARIA e148c83622 feat: experimental NVIDIA power control via RM ioctl interface
Adds an experimental power-cap mode using the undocumented RM ioctl
interface (based on panchovix's LACT PR #1205) to set power limits
below the VBIOS minimum (down to 30 W).

- hal/rm_power.py: RM ioctl power-cap read/write/reset + runtime probe
- limits.py: power_cap_mode (nvml/ioctl) with support detection
- config.py: persist power_cap_mode per GPU
- profiles: record/apply power_cap_mode
- server.py: POST /api/limits validates ioctl support (409 on failure)
- cli.py: profile save falls back to persisted mode
- client.py: power_cap_mode in Limits
- frontend: toggle + warning with panchovix attribution (LACT #1205)
- tests: test_rm_power.py (unit) + integration coverage
- Makefile: add test_rm_power.py to make test

Also includes automated linter reformatting (prettier, ruff, shellcheck,
isort, markdownlint) that the linter would apply anyway.
2026-09-17 22:44:27 +02:00
ARIA a8462e696c Add single-command installation: install.sh, Makefile, frontend build hook
- hatch_build.py: custom hatchling build hook that compiles the React
  frontend (npm ci + build) when frontend/dist is missing or stale, so
  'uv tool install git+https://gitea.zephyre.one/Pakobbix/nvcurve.git'
  works as a single command
- install.sh: curl|bash installer (checks prerequisites, auto-installs uv,
  clones and installs)
- Makefile: dev targets (frontend, install, dev, test, clean)
- README/docs: document the one-liner, clone, and direct-git installs
2026-09-16 10:43:23 +02:00
Pakobbix d810c44478 Merge pull request 'security: harden web server, daemon socket, and write paths' (#9) from security/hardening into main
Reviewed-on: #9
2026-09-10 14:20:57 +00:00
ARIA 39701c12ff security: harden web server, daemon socket, and write paths
Security review findings, fixed and verified:

Critical
- Fix unauthenticated arbitrary file read: the SPA catch-all route
  joined the raw URL path onto the dist dir without containment, so
  encoded '..' segments (/%2e%2e/etc/passwd) leaked any file readable
  by the root server. Resolve with realpath and reject paths outside
  the dist dir (fail-closed 404).

High
- Daemon socket: serve_start no longer accepts caller-chosen
  host/port. The socket is world-connectable (unprivileged CLI users),
  so callers could previously rebind the root web server to 0.0.0.0.
  The daemon now always binds the operator-configured address and
  reports it in the response; the CLI warns on mismatch.

Medium
- Remove the per-request max_delta_khz override from the API: the
  server-enforced safety cap is now authoritative. CLI direct paths
  (write, profile apply, verify) honor the configured cap; --max-delta
  still overrides for explicit root use.
- Snapshot restore: confine filepath to the snapshot directory
  (realpath containment; blocks symlink escapes).
- Login lockout: honor X-Forwarded-For only for peers listed in the
  new trusted_proxies config (rightmost untrusted hop), so the
  per-IP lockout works behind a reverse proxy. Spoofed headers from
  untrusted peers are ignored.
- /api/shutdown: new allow_api_shutdown config (default true);
  shared systems can disable the API shutdown path.

TLS (opt-in, like auth)
- New ssl_certfile/ssl_keyfile config + CLI flags (serve start,
  service install/configure, --no-ssl to disable). When active:
  HTTPS for UI/API, wss:// for WebSockets, Secure session cookie,
  CLI auto-switches to https://. Cert/key paths are validated up
  front with a clear error instead of a silent uvicorn crash.

Tests & docs
- tests/test_security.py: standalone regression tests (no new deps)
  covering SPA containment, snapshot containment, cap removal,
  client-IP derivation, proxy normalization, TLS scheme detection,
  and daemon host/port hardening.
- README + Usage-Guide: TLS section, new config keys, updated
  security notes.
2026-09-10 16:19:21 +02:00
Pakobbix 8956fc9d7b Merge pull request 'feat: full fan control — all fans or individual fans' (#8) from feat/multi-fan-control into main
Reviewed-on: #8
2026-09-10 13:50:55 +00:00
ARIA 6cb33187d3 feat: full fan control — all fans or individual fans
The fan curve previously only controlled fan index 0; secondary fans
stayed on driver control. The curve can now target all fans (new
default) or any individual fan(s).

Backend:
- hal/fans.py: get_num_fans() via nvmlDeviceGetNumFans; get_fan_info()
  returns per-fan speeds; set_fan_speed() accepts a fan index list
  (None = all fans; all-fans mode is lenient toward driver-locked
  fans, explicit lists are strict); reset_fan() restores all fans.
- server.py: per-GPU fan_targets state; the poller applies the curve to
  all target fans and logs write failures (once per distinct error);
  activation validates targets against the hardware (stale indices fall
  back to all fans); POST /api/fans accepts fans, POST /api/fans/speed
  accepts a fan index, GET /api/fans returns num_fans/fans/fan_targets.
- Persistence format is now {"curve": ..., "fans": ...}; legacy
  bare-curve entries migrate to "all fans" at startup.
- Profiles save/apply fan_targets alongside fan_curve.
- MonitoringSample carries per-fan speeds for live gauges.

Frontend:
- Fans tab: All / Fan 1 / Fan 2 / ... selector with live per-fan %;
  the selection is applied together with the curve.
- Live monitor: per-fan gauges with sparklines for multi-fan GPUs.
2026-09-10 15:48:28 +02:00
Pakobbix 34a9bc6d6e Merge pull request 'Clean up LSP diagnostics across backend and frontend' (#7) from cleanup/lint-modernization into main
Reviewed-on: #7
2026-09-08 21:58:42 +00:00
ARIA 930e56bd07 Clean up LSP diagnostics across backend and frontend
Backend (nvcurve/):
- hal/fans.py, hal/limits.py, hal/gpu.py: replace conditional pynvml
  imports with the established 'pynvml: Any = _pynvml_import' pattern
  (fixes ~50 'possibly unbound' errors); type the result dicts; guard
  query_interface() results; explicit uuid/pci-bus parsing (int, hex
  convention documented); modernize Optional[T] -> T | None
- cli.py: fix 'curve_state' possibly-unbound and snap_path None handling
  in cmd_setup; wrap unchecked int()/open()/makedirs() calls in
  try/except with clean CLI errors; add module logger for silent
  except-pass blocks; raise ... from exc; fix unused loop vars and
  set-comprehension
- hal/snapshot.py: filepath: str | None; wrap all file ops; sorted
  imports; remove unused CT_POINTS import
- daemon.py: extract 0o666 to _SOCKET_MODE constant (intentional for
  /run sockets) with nosemgrep
- server.py: nosemgrep for Python 3.7-compat false positive (project
  requires >= 3.12); log previously-swallowed exception
- profiles/native.py, profiles/apply.py: wrap file ops and int(k)
  profile-key parsing; sorted imports; modernize typing

Frontend (frontend/src):
- Add .js extensions to all relative imports (standard TS-ESM; Vite
  resolves .js -> .ts)
- React.FormEvent (deprecated in React 19 types) -> React.SubmitEvent
- catch (e: any) -> catch (e: unknown) + instanceof Error narrowing
- React-hooks: move ref writes from render into effects; convert
  viewport reset to render-phase state adjustment; split
  selectPoint(index, multi) into selectPoint + togglePoint (no flag
  argument); remove non-null assertion
- Static inline styles -> Tailwind classes (dynamic positioning/cursor
  styles kept)
- Remove non-standard 'container' option from scrollIntoView (browsers
  ignore unknown options) which had orphaned a @ts-expect-error
- Object.fromEntries for Map -> Record conversion

Tooling:
- .gitignore: ignore .codegraph/ local tool data

Verified: tsc --noEmit, vite production build, python imports, and
full LSP scan (0 errors/warnings in both projects).
2026-09-08 23:57:30 +02:00
Pakobbix fa944c9576 Update README.md 2026-09-02 15:44:49 +00:00
ARIA 9006c22fde Added new screenshots to show the available tabs/tools 2026-09-02 17:42:19 +02:00
ARIA b9d40f007d Added new screenshots to show the available tabs/tools 2026-09-02 17:41:58 +02:00
Pakobbix 418894a77b Update README.md 2026-09-02 15:34:43 +00:00
Pakobbix 06db52e8ac Merge pull request 'fix: fan settings persistence, profile indicator, and point removal' (#6) from fix/fan-settings into main
Reviewed-on: #6
2026-09-02 15:26:15 +00:00
ARIA d9260007d1 fix: fan settings persistence, profile indicator, and point removal
Address three fan-settings issues:

1. Profile view: show a fan icon next to a profile's name when it has a
   saved fan curve, so it's clear which profiles carry custom fans.

2. Fan curve persistence: the active fan curve was in-memory only and lost
   on every server restart. It is now persisted per-GPU in
   /etc/nvcurve/config.json (fan_curves) and re-applied at server startup,
   so an applied curve survives restarts. All fan-curve state changes route
   through _activate_fan_curve/_deactivate_fan_curve helpers that keep the
   persisted state in sync (apply, reset, and profile apply).

3. Point removal: the fan-curve remove button was nearly invisible. The
   chart remove control is now always faintly visible with an X glyph, the
   table remove button is larger with a tooltip, and a hint line explains
   how to add/remove points.

Also includes a formatting pass over the two edited frontend files.
2026-09-02 17:25:09 +02:00
ARIA 9cf2009d2c removed old markdown files 2026-09-02 16:58:31 +02:00
Pakobbix 0320cea895 Merge pull request 'feat: add Dashboard tab with live GPU overview' (#5) from feat/dashboard-tab into main
Reviewed-on: #5
2026-09-02 14:57:54 +00:00
ARIA da507d55bb feat: add Dashboard tab with live GPU overview
Add a new Dashboard tab as the default/first tab (Dashboard - Curve -
Performance - Fans) showing a full GPU overview.

Backend:
- New hal/dashboard.py: one-shot static GPU info via NVML (VBIOS, CUDA
  cores, compute capability, bus width, BAR1/CPU-accessible VRAM,
  Resizable BAR, PCIe max, max clocks, power limits, temp thresholds,
  persistence mode, fan count, serial, board part number, UUID).
  ROP count and VRAM type are best-effort from a per-model table since
  NVML does not expose them.
- New /api/dashboard endpoint.
- MonitoringSample gains live throttle_reasons (+label), PCIe link
  width/generation (downclocks when idle, so read per poll), and VRAM
  temp (from the MEMORY thermal sensor, if exposed).

Frontend:
- New Dashboard component: critical top row (throttling, voltage,
  GPU/VRAM temps), full live-monitor grid with sparklines, and a static
  GPU-information grid. Unavailable fields are omitted.
- useDashboard hook + DashboardInfo type + api.client dashboard().

Also: restrict CORS to localhost origins (end-anchored), make
_int_key_deltas fail closed on bad keys, and clean up lint blockers in
server.py/monitoring.py.
2026-09-02 16:56:47 +02:00
Pakobbix 3c55263250 Merge pull request 'feat: multi-user authentication (dual mode)' (#4) from feat/multi-user-auth into main
Reviewed-on: #4
2026-09-02 13:47:26 +00:00
ARIA bbd692ea2e feat: multi-user authentication (dual mode)
Add optional login protection for the web UI/API, intended for shared
machines (e.g. AI servers). Dual mode: with no users configured the API
and web UI are open (as before); once at least one user exists, every
/api/* and /ws/* endpoint requires a valid session.

- bcrypt password hashing: passwords stored as $2b$ hashes in
  /etc/nvcurve/users.json (0600, root-owned); plaintext never persisted.
- 24-hour sessions: HttpOnly cookie for browsers, Authorization: Bearer
  token for CLI/scripts; in-memory, invalidated on server restart.
- Multi-user: multiple named accounts (no shared-password mode).
- New CLI: nvcurve user add|list|remove|set-password (root for mutating
  ops; password always prompted, never a CLI argument).
- New endpoints: GET /api/ping (public), /api/auth/status|login|logout|users.
- Web UI: sign-in screen when auth is enabled; status bar shows the
  signed-in user with sign-out; expired sessions (401) re-show sign-in.
- Brute-force lockout: 10 failed logins/IP within 5 min -> 15 min lockout.
- New dependency: bcrypt.

Also: LSP config (pyrightconfig.json) pointing at the project .venv, and
small error-handling cleanups in daemon.py/server.py.
2026-09-02 15:21:35 +02:00
Pakobbix af23a10f25 Merge pull request 'fix: retry NvAPI init on autoload to handle early boot race' (#3) from fix/autoload-nvapi-retry into main
Reviewed-on: #3
2026-08-09 06:29:58 +00:00
hhofmann f417297713 fix: retry NvAPI init on autoload to handle early boot race
init_nvapi() previously called sys.exit(1) directly, which raised
SystemExit (uncaught by except Exception). The autoload subprocess
would die instantly if the NVIDIA driver wasn't fully loaded at boot.

- Raise RuntimeError from init_nvapi() so callers can handle failures
- Add 16-attempt retry loop (2s interval, 32s total) in run_autoload()
2026-08-09 08:28:59 +02:00
Pakobbix 0c1f853a5c Merge pull request 'docs: add source install instructions and systemd service guide' (#2) from docs/source-install-systemd into main
Reviewed-on: #2
2026-08-08 13:51:08 +00:00
ARIA 76b491b913 docs: add source install instructions and systemd service guide
- Replace PyPI-only install with full source build workflow (npm + uv)
- Add Blackwell GPU fork notice with extended memory offset and fan control
- Add systemd service section: install commands, unit file template, config reference
- Update docs/Installation.md to match npm + uv tool install . workflow
- Update docs/Overview.md with Blackwell-specific capabilities
2026-08-08 15:50:52 +02:00
Pakobbix d2d05bbe6d Merge pull request 'feat: add fan curve control via temperature-based fan speed curves' (#1) from feature/fan-curve into main
Reviewed-on: #1
2026-08-01 14:07:13 +00:00
ARIA 26afaeecfa Fix fan_mode type to match backend API ('auto' | 'curve') 2026-08-01 16:03:02 +02:00
ARIA 477c4becae feat: add fan curve control via temperature-based fan speed curves
- Backend HAL (hal/fans.py): NVML v2 fan read/set/reset with min/max queries
- Server endpoints: GET/POST /api/fans, POST /api/fans/reset, POST /api/fans/speed
- Background fan poller: reads GPU temp every 2s, interpolates fan speed from curve
- Profile integration: fan_curve field saved/applied, auto-restore on shutdown
- Frontend: FanCurveEditor (SVG chart with drag/add/delete points), FanMonitor sidebar
- App.tsx: three-tab layout (Curve, Performance, Fans)
- GaugeCard: optional history sparkline, Fan Mode card without sparkline
- fan_mode field populated as 'curve' or 'auto' in GET /api/fans
2026-08-01 15:59:39 +02:00
ARIA 7605d21e27 Bump version to 0.5.1 2026-05-09 15:27:58 +02:00
ARIA 67762b6a76 Document unreleased changes in changelog 2026-05-09 15:27:10 +02:00
ARIA 706df90bc3 Fix doc links: hyphenate filenames for clickable markdown links 2026-05-09 15:23:12 +02:00
ARIA a36b8c4dff Prune README and add comprehensive documentation
README reduced to essentials: install, quick start, and links to docs/.
New docs/ covers Overview, Installation, Usage Guide, and Tips and Tricks.
2026-05-09 15:22:25 +02:00
ARIA 024dcbceb0 nvcurve with some fixes and better limits 2026-05-09 15:05:29 +02:00