Download Latest Version remotepower-3.2.1.tar.gz (7.6 MB)
Email in envelope

Get an email when there's a new version of remotepower

Home / v3.0.4
Name Modified Size InfoDownloads / Week
Parent folder
README.md 2026-05-24 9.0 kB
v3.0.4 source code.tar.gz 2026-05-24 4.6 MB
v3.0.4 source code.zip 2026-05-24 4.8 MB
Totals: 3 Items   9.4 MB 0

Changelog

All notable changes to RemotePower. Newest first.

v3.0.4 — 2026-05-24

A bug-fix release hot on the heels of v3.0.3. Eight real production bugs, all landed the same evening they were spotted. Recommended for every operator who runs the AI features, the metric thresholds, the per-device settings drawer, or the mobile / PWA UI — i.e. nearly everyone. No schema changes, no migrations needed.

Fixed

  • AI chat returned 500 on every request. _http_post_json in ai_provider.py referenced cfg.get('insecure_ssl') from inside a function that never received cfg as a parameter. The reference resolved against an unbound name and raised NameError before the request ever left the box. The bug was latent in v3.0.2 — the change that "honoured" the insecure_ssl flag never wired it through — and triggered the first time a v3.0.2+ install actually exercised the chat path. Fix: explicit insecure_ssl: bool = False parameter; callers forward cfg.get('insecure_ssl'). Anthropic and OpenAI-compatible paths both updated. The matching _http_get_json got the same parameter for symmetry.

  • Monitor page showed "OK" badge while Needs Attention was screaming about a swap/memory/CPU warning on the same host. handle_devices_list() returned a curated subset of device fields and silently dropped metric_state. The client's row aggregator iterated d.metric_state || {}, got empty, and defaulted to "OK" — even though /api/attention read the same state on the server side directly and was correctly surfacing the alert. Fix: include metric_state in the device list response. The dict is small (one entry per active alert) and cheap to serialise.

  • No 🩺 Investigate button on memory/swap/CPU alerts. The AI prompt keys (mitigate_memory, mitigate_cpu) have existed in ai_provider.SYSTEM_PROMPTS since v3.0.1, but _MITIGATE_PLAYBOOKS only carried patches / disk / drift / service_down / reboot / brute_force. The alert kind landed in Needs Attention as 'swap' / 'memory' / 'cpu', no playbook lookup match, no button rendered. Fix: three new playbooks with concrete read-only diagnostics:

  • memory: free -h, /proc/meminfo top fields, top 20 by %mem, recent OOM events from journalctl + dmesg, vm.swappiness / overcommit sysctls, systemd-cgtop snapshot.
  • swap: free -h, swapon --show, per-process VmSwap ranking from /proc/*/status, vm.swappiness, PSI memory pressure, recent swap-related journal entries.
  • cpu: uptime, loadavg, top 20 by %cpu, processes in uninterruptible D-state, mpstat / iostat / vmstat for iowait, PSI CPU pressure. Each is explicitly marked non-destructive (test enforced). The client-side MITIGATE_KINDS set and _MITIGATE_KIND_LABELS dict were also updated in lockstep (they were a duplicate source of truth that previously had to be maintained manually) and a regression test asserts JS/Python parity.

  • "Save settings" button in the device drawer 404'd with "Not found". The drawer's _drawerSaveSettings() posts the full bundle (group, tags, icon, monitored, poll_interval, watched_services, log_watch, watched_files, cmd_allowlist) to POST /api/devices/<id> — but no bulk handler ever existed. The route fell through to the dispatcher's catch-all 404. New handle_device_save_bulk() accepts the bundle, validates every field with the same rules as the per-field PATCH endpoints, writes once atomically, and audits the save with the field list. The per-field endpoints still work and are unchanged. Two storage-name divergences are handled inside the bulk handler: client's watched_services is written as services_watched (server-side historical naming), and client's cmd_allowlist is written as allowed_commands (the canonical field _check_exec_allowlist() reads at command-execution time). The dispatcher route uses a slash-count guard so it cannot collide with any future /api/devices/<id>/<suffix> POST route.

  • "Re-run AI" on a mitigation playbook returned 200 OK with every field blank. _call_ai_with_prompts() passed arguments to chat_openai_compatible() in the wrong order — the messages parameter received the system-prompt STRING, then payload_messages.extend(messages) iterated it character by character and sent the LLM a messages array like [{role:'system', content:'Alert:...'}, 'Y', 'o', 'u', ...]. Ollama rejected the malformed payload, the provider returned {ok: False, ...}, and the caller's ai_result.get('text', '') happily returned ''. Fix: build a proper messages=[{role, content}] list, pass the system prompt as system, unpack the per-prompt overrides into matching kwargs. And — related — the handler now returns 502 with the actual provider error message rather than 200 with an empty body, and logs the traceback to stderr so future failures are diagnosable.

  • Mobile / PWA sidebar drawer wouldn't collapse. Tap-outside-to-close silently failed because the handler required e.target === document.body, but real browsers report the click target as the underlying <div id="app"> or .app-content rather than body itself. Burger-to-close also broken because the burger button (header z-index 100) sits behind the scrim (z-index 800) once the drawer is open, so the burger's own onclick never fires. Only the nav-button-click close path worked, which made the drawer feel half-broken. New handler uses explicit closest('.sidebar') / closest('.mobile-burger') / closest('.nav-btn') guards instead — any tap outside those zones closes the drawer on mobile. Regression test forbids the strict e.target === document.body pattern from sneaking back in.

  • "✨ AI Prioritise Updates" button felt unresponsive. Click, no in-place feedback, eventually a small toast that was easy to miss. Operators reported "I clicked it, nothing happened." Two changes: the button visibly disables and switches to ⏳ during the API call, and the negative-case toasts ("no patch history" / "no upgrade listing in history") got rewritten. Iter 2: the earlier "use Force re-scan packages" suggestion was misleading — force_package_scan only refreshes the upgradable COUNT, not the listing (the agent's get_patch_info() discards out and only keeps len()). The only path that populates patch_history with a real listing is an operator-triggered exec command. Rather than make the operator dig for that, ✨ now auto-queues the right per-package-manager listing command via POST /api/exec after a confirmation prompt (apt list --upgradable, dnf check-update, pacman -Qu). One click → wait ~60 s for the heartbeat → click ✨ again, AI engages.

  • Mobile burger button didn't close the open drawer. Tap-outside worked (v3.0.4 iter 1 fix), but tapping where the burger visually was had no effect on mobile Chrome / installed PWA. Root cause: the scrim (z-index 800, inset: 0) covers the burger (header z-index 100) once the drawer is open, so the burger's onclick never fires. The body-level close handler should catch it via the scrim — and does on desktop browsers' touch emulation — but real mobile Chrome and PWA installs were unreliable here. Standard mobile-drawer fix: a dedicated ✕ button inside the sidebar header, visible only at max-width: 720px. Always discoverable, always works, no z-order trickery.

  • Silent except → logged exceptions on the heartbeat metric path. handle_heartbeat() wrapped process_metric_thresholds() in a bare except Exception: pass. Any logic bug there silently broke metric state recompute and the operator got no clue. Now logs class: message

  • traceback to stderr (journalctl -u fcgiwrap) while still keeping the heartbeat path resilient.

Internals

  • Test suite at 1,532 teststest_v304.py holds the strict version pins now; test_v303.py's pins loosened to ^3\.\d+\.\d+$ regexes. Same convention test_v302.py followed for v3.0.3.
  • This release ships preliminary scaffolding for v3.1.0 (the mcp role enum, an empty MCP_ACTION_ALLOWLIST, the require_mcp_action() gate, a get_mcp_attribution() helper that reads X-MCP-Client / X-MCP-Prompt headers, optional ai_host / ai_prompt kwargs on audit_log, and a per-device require_confirmation field with its own PATCH endpoint). All of it is silent — no MCP write tools are yet registered, so even a valid mcp-role API key still gets 403 on every action attempt. Tests for the scaffolding live in test_v310.py. Stage 4 of v3.1.0 will populate the allowlist.
Source: README.md, updated 2026-05-24