Files
claudetools/session-logs/2026-05-13-session.md

42 KiB
Raw Blame History

GuruRMM Session — 2026-05-13

User

  • User: Mike Swanson (mike)
  • Machine: DESKTOP-0O8A1RL
  • Role: admin
  • Session span: ~06:0013:00 local (approx)

Session Summary

The session started with a /sync (clean, no changes) followed by a request to diagnose an RMM update failure on DESKTOP-0O8A1RL. Diagnosis proceeded by inspecting the running GuruRMMAgent service, the install directory, the ProgramData log files, and the agent source code in the local dev clone (projects/msp-tools/guru-rmm/).

Initial inspection found the agent binary reporting version 0.6.4 (via --version) while the registry HKLM\SOFTWARE\GuruRMM\Version still read 0.6.2. The .old backup file from a prior update remained uncleaned in the install directory, and a gururmm-agent.backup file persisted in ProgramData. Logs revealed two back-to-back updates had occurred overnight (0.6.2→0.6.3 at 00:44 UTC, 0.6.3→0.6.4 at 03:04 UTC) with restart gaps of 65 minutes and 3 hours 27 minutes respectively — far longer than any deliberate delay.

Deep reading of updater/mod.rs, watchdog/monitor.rs, watchdog/pipe.rs, transport/websocket.rs, and server/src/ws/mod.rs produced a full causal chain: the Windows service exits with code 0 after binary replacement (bypassing SCM recovery), the detached cmd restart helper is likely killed by the Windows job object, the GuruRMMWatchdog service is not installed on this machine (so the IPC path always fails), the rollback PS1 uses Get-Service which silently returns null for GuruRMMAgent, the watchdog reads a non-existent agent.toml for the server URL, and post-update cleanup (cancel_rollback_watchdog, cleanup_backup) is never triggered because the server sends AuthAck before computing the update confirmation.

Nine bugs were filed as tracked tasks (#1#9). A design discussion followed about whether to fix the current system (Option A) or replace it with MSI-based updates. The consensus was Option A with an architectural direction that the watchdog will eventually take over as the primary updater, with the main agent retaining self-update as permanent fallback. An approved plan was written, delegated to the Coding Agent, and all changes were implemented across 7 files. A Code Review Agent was launched (in background at session end — result pending).


Key Decisions

  • Option A (fix 9 bugs) over MSI-based updates — MSI approach is cleaner long-term but requires significant build pipeline changes. Option A ships in one sprint. Decision to revisit MSI for Windows updates in a future phase.

  • Watchdog owns stop+replace+start; agent owns download+verify — When the watchdog eventually implements PerformUpdate, the IPC carries a local staged path, not a URL. This keeps the watchdog free of HTTP client logic and avoids duplicating the agent's download+ checksum machinery.

  • Main agent retains full self-update as permanent fallback — Even with the watchdog as primary updater, the agent falls through to its own update logic if the IPC fails. Belt and suspenders.

  • Exit(1) not exit(0) as SCM fallback — When both IPC paths (PerformUpdate and RestartMainService) fail, the agent now exits with code 1 so SCM recovery fires within 10s. The IPC-success path still exits 0 (watchdog owns restart in that case).

  • SCM recovery delay changed 60s → 10s — The original 60s delay was unnecessarily slow for the fallback case. 10s is aggressive enough to be useful without thrashing.

  • PerformUpdate IPC variant added now (returns not-implemented) — Adding the command to the protocol now means no agent-side protocol change is needed when the watchdog implements it. The interface is locked; the implementation is deferred.

  • complete_update_by_agent() was already written but never called — The DB function existed in server/src/db/updates.rs exactly matching the needed signature. The fix was purely a wiring change in server/src/ws/mod.rs, not new code.

  • Watchdog server URL: compile-time constant, not registry — The watchdog is the same binary, compiled with the same GURURMM_SERVER_URL env var. Reading a TOML file was architecturally wrong and the file didn't exist anyway. The fix uses option_env! directly.


Problems Encountered

  • Get-Service -Name "GuruRMMAgent" returns nothing — PowerShell silently fails to enumerate the service even with the exact name; sc.exe queryex finds it fine. Root cause unclear (likely a non-elevated session permission issue). Resolution: all PS1-based service checks in the codebase replaced with sc.exe query equivalents.

  • Post-update cleanup path never reaches cleanup_backup() — The server was sending AuthAck before running the post-update check, so the agent never received any signal to clean up. Resolution: compute update_confirmed before building AuthAck; include it in the ack; agent acts on it in the AuthAck handler.

  • Detached cmd restart killed by job object — Windows service processes are often placed in a job object with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE. When the service exits, child processes (the detached cmd) are killed before sc.exe start runs. Resolution: added CREATE_BREAKAWAY_FROM_JOB (0x01000000) flag alongside CREATE_NO_WINDOW; combined value 0x09000000. Also adding exit(1) fallback so SCM recovery fires regardless.

  • Code review still in progress at /save time — Code Review Agent was launched as a background task. Result pending. Changes should not be deployed until review is clean.


Configuration Changes

Files modified

  • agent/src/registry.rs — added write_version(), write_server_url(), read_server_url() with full platform stubs
  • agent/src/service.rs — added registry version+URL write on startup; SCM recovery delay 60s→10s; watchdog co-installation in install_service()
  • agent/src/updater/mod.rs — PerformUpdate IPC attempt (step 2.5 in do_update); 0x09000000 creation flags; exit(1) final fallback; sc.exe in PS1 rollback template replacing Get-Service
  • agent/src/watchdog/pipe.rsPerformUpdate variant added to WatchdogCommand enum (staged_path model)
  • agent/src/watchdog/monitor.rsread_server_api_url() replaced with server_api_url() using compile-time constant; PerformUpdate match arm added (logs + no-op, pipe server sends ok:false)
  • agent/src/transport/mod.rsupdate_confirmed: Option<Uuid> with #[serde(default)] added to AuthAckPayload
  • agent/src/transport/websocket.rs — cleanup block in AuthAck handler: calls cancel_rollback_watchdog() + cleanup_backup() when update_confirmed is Some
  • server/src/ws/mod.rsupdate_confirmed: Option<Uuid> added to server-side AuthAckPayload; complete_update_by_agent() wired up before AuthAck is sent; old post-ack update check block removed

Credentials & Secrets

None created or discovered this session.


Infrastructure & Servers

  • GuruRMM server: 172.16.3.30:3001 (Rust/Axum)
  • Build pipeline: Gitea push → webhook-handler.py (172.16.3.30:9000) → build-agents.sh → Pluto (172.16.3.36) for Windows MSI
  • Agent on DESKTOP-0O8A1RL:
    • Binary: C:\Program Files\GuruRMM\gururmm-agent.exe (4,452,648 bytes — v0.6.4)
    • Registry: HKLM\SOFTWARE\GuruRMM — SiteId: d008c7d4-9e5e-4666-9fa0-b432609d54cc, AgentKey: agk_ybg4Ty6zXU_2Ee0ddlUUtuZdz0B9Qw4_
    • Logs: C:\ProgramData\GuruRMM\agent.log.YYYY-MM-DD
    • Backup: C:\ProgramData\GuruRMM\gururmm-agent.backup (0.6.3 binary, 4,303,656 bytes — not yet cleaned up pending code review + deploy)
    • Service: GuruRMMAgent (PID 4856 at session start) — GuruRMMWatchdog NOT YET INSTALLED

Commands & Outputs

# Service query (use sc.exe, not Get-Service — Get-Service silently fails for GuruRMMAgent)
sc.exe queryex "GuruRMMAgent"
# → STATE: 4 RUNNING, PID: 4856

# Registry state at session start
Get-ItemProperty "HKLM:\SOFTWARE\GuruRMM"
# → Version: 0.6.2 (stale — binary was actually 0.6.4)
# → SiteId: d008c7d4-9e5e-4666-9fa0-b432609d54cc
# → AgentKey: agk_ybg4Ty6zXU_2Ee0ddlUUtuZdz0B9Qw4_

# SCM recovery (was 60s, changed to 10s in code)
sc.exe qfailure "GuruRMMAgent"
# → FAILURE_ACTIONS: RESTART/60000/RESTART/60000/RESTART/60000 (old config)

# Watchdog not installed
sc.exe queryex "GuruRMMWatchdog"
# → [SC] OpenService FAILED 1060 (service does not exist)

# Log entries confirming the two overnight updates
# 2026-05-13T00:44:24Z  0.6.2 → 0.6.3 update started; restart at 00:44:25; agent back at 01:49:47 (65 min gap)
# 2026-05-13T03:04:28Z  0.6.3 → 0.6.4 update started; restart at 03:04:29; agent back at 06:31:27 (3h27m gap)

Pending / Incomplete Tasks

  • Code Review Agent result pending — launched as background task at session end. Do NOT build or deploy to the server until the review is clean. If issues are found, address them in a follow-up session.

  • Build and deploy — once code review is clean:

    1. Push to Gitea gururmm repo (triggers build pipeline)
    2. Server binary (v0.6.5 per coord API, Pending build+deploy) needs to be deployed first
    3. After server deploy, agents will receive update push to new agent version
    4. Verify on DESKTOP-0O8A1RL: registry version updated, backup cleaned, GuruRMMWatchdog installed
  • GuruRMMWatchdog not installed on existing endpoints — the co-install logic in service.rs triggers during the install flow, which existing enrolled agents won't re-run automatically. Options: (a) add a one-time watchdog install step to the update post-restart sequence, (b) run a remediation script via the dashboard command channel, or (c) accept that watchdog deploys on next MSI re-run.

  • 9 bug tasks (#1#9) need status updates — tasks filed in the task system but not yet marked complete; should be updated once code review passes and the build is deployed.

  • Bug #2 (Get-Service returns nothing) — root cause unresolved. The fix (replacing Get-Service with sc.exe everywhere in PS1 templates) is in, but the underlying service visibility issue should be investigated if time permits.

  • Server component stategururmm/server is at state built v0.6.5 per coord API, pending deploy. gururmm/agents at state built v0.6.4. Both need deploy after build.


Reference Information

  • Coord API component states: GET http://172.16.3.30:8001/api/coord/status
  • Session plan file: C:\Users\guru\.claude\plans\ticklish-questing-stallman.md
  • Relevant source files:
    • agent/src/updater/mod.rs — full update flow, rollback, restart logic
    • agent/src/watchdog/monitor.rs — SCM health monitor, alert posting, server URL
    • agent/src/watchdog/pipe.rs — IPC protocol, WatchdogCommand enum
    • agent/src/transport/websocket.rs — WebSocket client, AuthAck handler, update trigger
    • server/src/ws/mod.rs — server-side WS handler, authenticate(), AuthAck, update dispatch
    • server/src/db/updates.rscomplete_update_by_agent() (was unhooked, now wired)
  • Key bug notes:
    • PerformUpdate IPC: currently returns ok:false (not implemented in watchdog) — agent falls through to self-update. Future: watchdog implements stop+replace binary at staged_path+start.
    • exit(1) is the final SCM safety net — SCM recovery fires within 10s (after config change deploys)
    • AuthAckPayload.update_confirmed is Option<Uuid> with #[serde(default)] on agent side — backwards compatible with older servers

Update: 14:50 PT — Update channel selection + server v0.3.1 deploy

Session Summary

This update session began by confirming the v0.6.4 agent build (including all 9 auto-update reliability fixes and ensure_watchdog_running) had completed successfully on Pluto at 14:22 UTC. All Windows and Linux agent binaries were present in /var/www/gururmm/downloads/.

The main work was implementing the update channel selection feature: a stable/beta hierarchy allowing partners, clients, sites, and individual machines to opt-in to beta releases without affecting production machines. The design approved was DB-column + UI surface. A Coding Agent implemented all server and dashboard changes — migration 026, resolve_agent_channel() DB function, channel-aware get_latest_version()/needs_update() in the scanner, three new PATCH /api/{agents,sites,clients}/:id/channel endpoints, GET /api/agents/:id/effective-channel, and a UpdateChannelSelector React component wired into AgentDetail, SiteDetail, and ClientDetail pages. Server compiled clean (68 warnings, all pre-existing).

Deployment encountered two problems. First, migration 026 was applied manually via psql before the server binary was deployed, leaving the _sqlx_migrations table without a tracking row. When the new server binary started, sqlx tried to run migration 026 again and hit "column already exists". Resolution: deleted the bad row, changed the migration to use ADD COLUMN IF NOT EXISTS, pushed c1b8b80, and rebuilt. Second, the build-server.sh script always appends to the shared build log, and the background monitor's grep pattern matched an old "failed to start" line from the first attempt — causing a false "completed" notification. The second rebuild is currently running.

The build-agents.sh script was also updated to support a --channel flag. When invoked with --channel beta, it writes a .channel sidecar file ("beta") alongside each binary; stable builds remove any existing sidecar. This is the production mechanism for tagging beta releases. Backup build-agents.sh.bak-pre-channel was created before the edit.

Key Decisions

  • Never manually pre-apply migrations — sqlx owns migration state through _sqlx_migrations. Manually running psql diverges the tracking table from the schema, causing checksum failures on the next startup. Correct procedure: let the server binary apply its own migrations on startup. If pre-applying is necessary (e.g., zero-downtime column add), always make migrations idempotent with IF NOT EXISTS from the start.

  • build-agents.sh --channel beta writes sidecar, stable builds remove it — cleanup on stable builds prevents stale .channel files from a prior beta build being mistaken for a beta tag on the stable binary.

  • Channel resolution: agent → site → client → "stable" — three-level inheritance. resolve_agent_channel() uses a single JOIN query. Beta channel gets the absolute latest binary (any channel tag); stable gets only binaries tagged "stable" (no sidecar = stable).

  • All new DB queries use sqlx::query() not sqlx::query!() macros — avoids needing cargo sqlx prepare after each new query. The offline cache (server/.sqlx/) only needs updating for compile-time query! macros. This is the pattern all new code should follow to simplify builds.

Problems Encountered

  • sqlx migration conflict (column already exists): Manually applied migration 026 via psql left the _sqlx_migrations table without an entry for version 26. On server startup, sqlx attempted to run migration 026, hit "column already exists". Resolution: deleted the invalid row, updated migration to use IF NOT EXISTS, rebuilt.

  • Background monitor false completion: The until grep -q 'Server build complete\|failed to start' polling loop matched an old "failed to start" line from the first server build attempt in the shared log file. The second rebuild was still in progress when the monitor fired. Resolution: used a line-count guard in a new background wait command; second build still in progress at save time.

  • Server restart loop during failed deploy: The first v0.3.1 binary (with the migration conflict) caused systemd to restart the process repeatedly. The old binary was already overwritten, so the server was down until the second build completed. No data loss; agents reconnect automatically.

Configuration Changes

Committed to gururmm repo (4035b5c, 3df5880, c1b8b80):

  • server/migrations/026_update_channels.sql — new; ADD COLUMN IF NOT EXISTS update_channel TEXT CHECK (...) on clients, sites, agents
  • server/src/db/updates.rs — added resolve_agent_channel(), set_agent_channel(), set_site_channel(), set_client_channel()
  • server/src/updates/scanner.rsAvailableVersion.channel field; .channel sidecar file read; get_latest_version/needs_update accept channel: &str
  • server/src/ws/mod.rs — both needs_update call sites (connect + heartbeat) now resolve agent channel first
  • server/src/api/agents.rstrigger_update uses agent effective channel; added set_agent_channel_handler, get_effective_channel_handler
  • server/src/api/sites.rs — added set_site_channel_handler
  • server/src/api/clients.rs — added set_client_channel_handler
  • server/src/api/mod.rs — registered 4 new routes with patch routing
  • server/Cargo.toml — version bumped 0.3.0 → 0.3.1
  • dashboard/src/api/client.tsUpdateChannel type, EffectiveChannel interface, channelApi, update_channel field on Agent/Site/Client
  • dashboard/src/components/UpdateChannelSelector.tsx — new component: inherit/stable/beta selector with effective-channel label
  • dashboard/src/pages/AgentDetail.tsx — added UpdateChannelSelector to info section
  • dashboard/src/pages/SiteDetail.tsx — added UpdateChannelSelector
  • dashboard/src/pages/ClientDetail.tsx — added UpdateChannelSelector

Modified on server directly (not in git):

  • /opt/gururmm/build-agents.sh — added --channel arg parsing; writes .channel sidecar for beta builds; backup at .bak-pre-channel

Applied to production DB:

  • Migration 026 applied (columns already exist; tracking row inserted and will be re-inserted cleanly on next startup with idempotent migration)

Dashboard:

  • Built and deployed to /var/www/gururmm/dashboard/ from /home/guru/gururmm/dashboard/dist/

Infrastructure & Servers

  • GuruRMM server: 172.16.3.30:3001 (Rust/Axum) — v0.3.1 binary deployed, second build in progress (startup was failing due to migration conflict; now resolved with IF NOT EXISTS)
  • Build pipeline: /opt/gururmm/build-server.sh — builds server on 172.16.3.30 directly; /opt/gururmm/build-agents.sh — builds agents (Linux on server, Windows on Pluto 172.16.3.36)
  • Downloads: /var/www/gururmm/downloads/ — v0.6.4 agent binaries (all platforms) present and ready
  • Dashboard: /var/www/gururmm/dashboard/ (nginx-served) — updated with channel selector UI

Commands & Outputs

# Apply migration 026 (manual, before server had IF NOT EXISTS)
psql 'postgres://gururmm:43617ebf7eb242e814ca9988cc4df5ad@localhost:5432/gururmm' \
  -f /home/guru/gururmm/server/migrations/026_update_channels.sql
# → ALTER TABLE x3

# Build and deploy dashboard
cd /home/guru/gururmm/dashboard && npm run build
# → 2559 modules, dist/assets/index-*.js 1082kB, built in 11s
sudo cp -r /home/guru/gururmm/dashboard/dist/* /var/www/gururmm/dashboard/

# Delete bad sqlx migration row (checksum mismatch fix)
echo "DELETE FROM _sqlx_migrations WHERE version = 26;" | \
  psql postgres://gururmm:43617ebf7eb242e814ca9988cc4df5ad@localhost:5432/gururmm -f /dev/stdin
# → DELETE 1

# Beta build usage (once channel feature is deployed):
sudo /opt/gururmm/build-agents.sh --channel beta

# Trigger server rebuild
sudo bash -c 'nohup /opt/gururmm/build-server.sh >> /var/log/gururmm-build.log 2>&1 &'

Pending / Incomplete Tasks

  • Server v0.3.1 second build in progressbuild-server.sh running at save time. Monitor when it completes: tail -20 /var/log/gururmm-build.log. If it shows "Server build complete: v0.3.1", verify systemctl is-active gururmm-server returns active.

  • Watchdog not installed on DESKTOP-0O8A1RLGuruRMMWatchdog service still not present. Will self-install via ensure_watchdog_running() when the agent receives and applies the v0.6.4 update push. Agent will receive the update push once the server is back online.

  • SQLX issue needs permanent fix — see user question: "Can the SQLX issue be permanently resolved?" The fix is to always write ADD COLUMN IF NOT EXISTS in migrations and to never manually pre-apply them via psql. Additionally, set up a cargo sqlx prepare step in the build pipeline for any future migrations using query!() macros.

  • Verify DESKTOP-0O8A1RL watchdog after agent update — once server is running and agent updates, confirm: sc.exe queryex GuruRMMWatchdog shows RUNNING, registry version updated, backup file cleaned.

  • 9 bug tasks (#1#9) still need TickTick status updates — not yet marked complete.

Reference Information

  • Commits: bdb751b (bugs), e52ee19 (watchdog self-install), 5b43fe6 (build fixes), 4035b5c (channel feature), 3df5880 (version bump), c1b8b80 (idempotent migration)
  • Server DB: postgres://gururmm:43617ebf7eb242e814ca9988cc4df5ad@localhost:5432/gururmm
  • Build log: /var/log/gururmm-build.log (shared between agent and server builds)
  • New API endpoints:
    • PATCH /api/agents/:id/channel — set agent channel
    • PATCH /api/sites/:id/channel — set site channel
    • PATCH /api/clients/:id/channel — set client channel
    • GET /api/agents/:id/effective-channel — resolved channel + source
  • Scanner sidecar convention: <binary-filename>.channel containing "beta"; absent = stable

Update: 11:50 PT — IPC pipe DACL fix, CORS fix, dashboard clients bug

Session Summary

This session was a continuation from a prior context window. The primary carried-over issue was the Windows agent system tray showing "disabled" — the tray icon connects to the agent service via a named pipe (\\.\pipe\gururmm-agent) created by SYSTEM, but user-session processes (non-elevated tray) were getting Access Denied when trying to open the pipe client end.

Four attempts were required to land the DACL fix. v0.6.8 tried security_descriptor() on ServerOptions (method doesn't exist). v0.6.9 tried SetKernelObjectSecurity post-creation with DACL_SECURITY_INFORMATION (compiled, but failed at runtime with 0x80070005 — Access Denied — because the tokio pipe handle lacks WRITE_DAC). v0.6.10 confirmed that approach is fundamentally broken: CreateNamedPipeW with PIPE_ACCESS_DUPLEX does not grant WRITE_DAC, so post-creation DACL modification is not possible via that handle. v0.6.11 switched to CreateNamedPipeW with a SECURITY_ATTRIBUTES struct holding a NULL DACL, but hit a compile error because PIPE_ACCESS_DUPLEX and PIPE_TYPE_BYTE are not exported by the windows 0.58 crate from Win32::System::Pipes. v0.6.12 resolved this by using raw integer literals with the correct wrapper types: FILE_FLAGS_AND_ATTRIBUTES(0x00000003 | 0x40000000) and NAMED_PIPE_MODE(0). That build compiled and the tray connected successfully ("IPC: tray client connected" in agent log).

A Firefox CORS bug was identified from a browser console export. The server was sending Access-Control-Allow-Headers: * (wildcard), and Firefox enforces that the Authorization header must be explicitly listed — the wildcard does not cover it. This caused Firefox to block API requests with warnings (escalating to errors in newer Firefox). The fix changed the Axum CORS layer from .allow_headers(Any) to .allow_headers([AUTHORIZATION, CONTENT_TYPE, ACCEPT]). A concurrent build error in server/src/ws/mod.rs was also fixed: fail_agent_update expects Option<&str> for its error message parameter, but the call site was passing &str directly. Both fixes were deployed as server v0.3.1.

The dashboard was then reporting no clients at all. Investigation showed the server API returning 200 with 0 ms latency for /api/clients — indicating an immediate empty-list return with no database query. The root cause was in authz/permissions.rs: accessible_client_ids() only returns None (meaning all clients) for the dev_admin role, but all four users in the production database have role = "admin" (the legacy superuser role predating the multi-tenant system). With admin role and no org memberships in the JWT, the function returns Some([]), triggering the early-exit branch. The fix added is_admin() to AuthContext covering both admin and dev_admin, and updated accessible_client_ids(), can_access_org(), is_org_admin(), and all the org management permission methods to use it. Deployed as a server rebuild (same v0.3.1 version number, uptime reset to 27s confirmed via /status).

Key Decisions

  • CreateNamedPipeW + SECURITY_ATTRIBUTES at creation time, not SetKernelObjectSecurity post-creationSetKernelObjectSecurity requires WRITE_DAC access on the handle, which tokio's CreateNamedPipeW-backed NamedPipeServer does not have. The only correct approach is to pass the security descriptor at pipe creation time.

  • Raw integer literals for windows 0.58 pipe constantsPIPE_ACCESS_DUPLEX and PIPE_TYPE_BYTE are simply not re-exported by the windows crate 0.58 from Win32::System::Pipes. Using FILE_FLAGS_AND_ATTRIBUTES(0x00000003 | 0x40000000) and NAMED_PIPE_MODE(0) directly is correct and stable — the Win32 ABI values do not change.

  • admin role treated as full superuser in all permission checks — the admin role is explicitly documented as the legacy superuser before dev_admin was introduced. Restricting it to org-membership-based access is a regression. All production users are admin. The correct fix is to treat admin the same as dev_admin in is_admin(), not to forcibly re-issue JWTs or update the DB.

  • Tray binary crash investigation deferred — the tray was crashing/exiting every 30 seconds (detected at the 30-second reap_dead poll boundary). Root cause is likely the OLD tray binary on enrolled machines (never updated by the agent auto-updater, which only updates the agent binary). The gururmm-tray-windows-amd64-{version}.exe is built and deployed to the downloads server but never pushed to agents. This is a separate deferred issue — auto-update flow needs to also update the tray binary.

  • Stale build lock must be cleared manually/var/run/gururmm-build.lock is left by zombie build processes after failures. Added rm -f /var/run/gururmm-build.lock before each build trigger. The build script should defensively remove stale locks on startup (future fix).

Problems Encountered

  • SetKernelObjectSecurity fails at runtime with 0x80070005 — post-creation DACL modification is impossible on a tokio pipe handle. Required full approach switch to CreateNamedPipeW with SECURITY_ATTRIBUTES.

  • PIPE_ACCESS_DUPLEX not in windows 0.58 — constant exists in Win32 SDK headers but is not re-exported by the crate. Used FILE_FLAGS_AND_ATTRIBUTES(0x00000003 | 0x40000000) as the solution.

  • Stale /var/run/gururmm-build.lock — blocked both the agent build and server build triggers. Had to sudo rm -f before each trigger.

  • fail_agent_update type mismatch — function signature was Option<&str> but call site in ws/mod.rs passed raw &str. Caught as a compile error during the CORS server build.

  • All dashboard users have role = "admin" — the production DB was seeded before the dev_admin role was introduced. The permission system assumed admin users would have dev_admin. Every API endpoint that gate-checked is_dev_admin() only was effectively broken for the entire production user base.

Configuration Changes

Modified in gururmm repo (committed and pushed):

  • agent/src/ipc.rscreate_server_pipe() rewritten using CreateNamedPipeW + NULL DACL SECURITY_ATTRIBUTES; added windows::Win32::System::Pipes and Win32::Storage::FileSystem feature usage
  • agent/Cargo.toml — version 0.6.11 → 0.6.12; added Win32_System_Pipes and Win32_Storage_FileSystem to windows feature list
  • server/src/main.rs — CORS: .allow_headers(Any).allow_headers([AUTHORIZATION, CONTENT_TYPE, ACCEPT]); added use axum::http::header::{ACCEPT, AUTHORIZATION, CONTENT_TYPE}
  • server/src/ws/mod.rsfail_agent_update call: "send_to failed: ..."Some("send_to failed: ...")
  • server/src/authz/permissions.rs — added is_admin() method; accessible_client_ids(), can_access_org(), is_org_admin(), can_set_org_limits(), can_impersonate(), can_create_org(), can_delete_org() all updated to use is_admin()
  • server/src/api/organizations.rs — all auth.is_dev_admin() calls replaced with auth.is_admin()

Deployed to production:

  • Agent v0.6.12 binary on download server, update scanner dispatching to all 48 enrolled agents
  • Server v0.3.1 (two builds: first fixed CORS + ws/mod.rs; second fixed permissions)

Credentials & Secrets

  • GuruRMM DB (production): postgres://gururmm:43617ebf7eb242e814ca9988cc4df5ad@localhost:5432/gururmm (rediscovered during permissions investigation — matches prior session log)

Infrastructure & Servers

  • GuruRMM server: 172.16.3.30:3001 — v0.3.1, uptime ~27s at last check (18:10 UTC restart)
  • Build servers: 172.16.3.30 (Linux agent + server); 172.16.3.36 Pluto (Windows agent + tray + MSI)
  • Downloads: /var/www/gururmm/downloads/gururmm-agent-windows-amd64-0.6.12.exe + tray binary present
  • Enrolled agents: 48 total, 10 online, 38 offline (at 18:10 UTC)
  • Tray binary on enrolled machines: OLD version (never auto-updated) — crashes ~30s after connecting to new NULL-DACL pipe

Commands & Outputs

# Check guruRMM DB users (diagnosed empty dashboard)
PGPASSWORD=43617ebf7eb242e814ca9988cc4df5ad psql -U gururmm -h localhost -d gururmm \
  -c 'SELECT id, email, role FROM users;'
# → All 4 users have role="admin" (not dev_admin)

# Verify clients table has data
PGPASSWORD=... psql ... -c 'SELECT COUNT(*) FROM clients;'
# → 15

# Check server status after rebuild
curl -s https://rmm-api.azcomputerguru.com/status
# → {"version":"0.3.1","uptime_seconds":27,"agents":{"total":48,"online":10},...}

# Clear stale build lock
sudo rm -f /var/run/gururmm-build.lock

# Trigger server rebuild
nohup sudo bash /opt/gururmm/build-server.sh > /tmp/server-build-$$.log 2>&1 &

Pending / Incomplete Tasks

  • Tray binary never auto-updatedgururmm-tray.exe in agent install dirs on enrolled machines is the original install version. The auto-updater only replaces the agent binary. Fix: extend the update flow to also download and install the new tray binary alongside the agent binary. New server UpdatePayload field + agent-side tray download step needed.

  • Tray crash root cause — the tray exits ~30s after connecting (detected at the 30-second reap_dead poll). Likely old binary crashing after connecting to the now-accessible pipe. Unconfirmed — no tray crash log (windows_subsystem = "windows" swallows panics). Adding file logging to the tray is the correct diagnostic step.

  • /var/run/gururmm-build.lock stale lock protection — build script should rm -f the lock at startup to avoid manual intervention on the next build failure. Simple one-line addition to build-agents.sh and build-server.sh.

  • Plan file bugs #1#9 — the ticklish-questing-stallman plan still has unfinished items (registry version write, watchdog co-install, etc.) that were not addressed this session since the session focused on the tray pipe DACL fix and the production dashboard break.

Reference Information

  • Commits (gururmm repo):
    • a6b3174 — fix: grant legacy admin role full access in permission checks
    • 8c7380c — fix(server): wrap fail_agent_update error_message in Some()
    • f3d0cc0 — fix(server): explicitly list Authorization in CORS allow_headers
    • 47128b5 — fix(ipc): use FILE_FLAGS_AND_ATTRIBUTES raw values
    • 57ff059 — fix(ipc): create pipe with NULL DACL via CreateNamedPipeW+SECURITY_ATTRIBUTES
  • Key source files:
    • agent/src/ipc.rs:156create_server_pipe() with NULL DACL (native-service feature)
    • server/src/authz/permissions.rs:35is_admin() method
    • server/src/api/clients.rs:49accessible_client_ids() usage (empty-list path)
  • Production DB connection: postgres://gururmm:43617ebf7eb242e814ca9988cc4df5ad@localhost:5432/gururmm
  • Tray binary download server path: /var/www/gururmm/downloads/gururmm-tray-windows-amd64-{version}.exe

Update: ~17:00 PT — Policy UI defaultHint fix + full end-to-end policy wiring

Session Summary

This session completed two major policy system items. First, a UI fix: the policy editor's "Default (ON/OFF)" radio hints were hardcoded strings that didn't reflect the actual system default policy data. After the system default policy was updated in the prior session to disable all metrics and auto-update while keeping tray settings, the hints still showed "Default (ON)" for disabled fields. The fix added a systemDefaults?: PolicyData prop to PolicySectionEditor, computed a hints map from the live system default policy data using a boolHint() helper, and replaced all 16 hardcoded defaultHint="ON/OFF" values with hints.xxx references. The system default policy data is fetched via policiesApi.getSystemDefault() (already in the page's queries) and passed down through PolicyDetailPolicySectionEditor. Dashboard was built and deployed (commit 287d106).

Second, a full audit of the policy system revealed that agents never receive policy data despite the infrastructure being in place. The ConfigUpdate WebSocket message existed in the protocol but was never sent by the server; AppState had no policy field; every subsystem (metrics, tray, updates, watchdog) used hardcoded defaults. The only wired section was thresholds, which are evaluated server-side when metrics arrive.

An implementation plan was approved covering 10 files across both the agent and server crates. A Coding Agent (Opus, 14 minutes) implemented all changes: expanded ConfigUpdatePayload from 2 fields to 4 nested section structs covering all policy sections; added AppState::effective_policy: RwLock<ConfigUpdatePayload>; replaced the ConfigUpdate stub with a real handler that stores policy and forwards the watchdog section via IPC; changed the fixed-interval metrics loop to a sleep-based loop reading interval + collect flags from policy each iteration; added collect_with_flags() to MetricsCollector; wired GetPolicy IPC to read from AppState instead of default_permissive(); added UpdateConfig to WatchdogCommand and handled it in the monitor with runtime config; added policy_to_agent_config() server-side helper in a new server/src/policy/config_update.rs; wired ConfigUpdate dispatch after AuthAck in server/src/ws/mod.rs; gated auto-update push on updates.auto_update from effective policy; and added push_config_update_to_affected() called from both assignment create and delete endpoints. Both crates built clean (agent on server via cargo check, server via build-server.sh — 68 warnings, all pre-existing). Deployed as server v0.3.1 (commit 78b6831).

Key Decisions

  • hints computed inside PolicySectionEditor from systemDefaults prop — avoids threading per-field hint strings through the prop chain; one boolHint() call per field at render time. When systemDefaults is undefined (before query resolves), all hints default to "ON" to match the system default seeded in migration 027.

  • systemDefaultData passed to both <PolicyDetail> call sites — the system default detail view passes systemDefault.policy_data to itself (showing its own values as hints); regular policies pass systemDefault?.policy_data (reflecting the true baseline).

  • Agent collect_with_flags() zeroes disabled fields rather than skipping collection — avoids restructuring the single-pass collect() method. Disabled metrics send 0/None in the payload; server receives them but evaluates thresholds against 0, which never triggers alerts (effectively disabled). Simplest correct approach without breaking the existing collect architecture.

  • Server-side mirror structs in policy/config_update.rs, not re-using agent types — agent and server are separate crates. The server serializes AgentConfigUpdate to JSON; the agent deserializes to ConfigUpdatePayload. JSON field names match exactly. Mirror structs keep the crates fully decoupled.

  • Single get_effective_policy() call per connect, result reused — computed once after registration, used for both ConfigUpdate dispatch and auto-update gating. Avoids two round-trips to the DB on every agent connect.

  • push_config_update_to_affected() spawned async (non-blocking) — assignment change response returns immediately; push happens in background. If the push fails (agent disconnected between check and send), it's a no-op — next connect will receive the current policy.

  • Watchdog UpdateConfig applies per-field (None = no change) — consistent with the rest of the ConfigUpdatePayload design; partial updates won't reset unrelated watchdog fields.

  • Watchdog interval floored at 5 seconds — prevents a policy misconfiguration from creating a CPU-spinning tight loop in the watchdog process.

Problems Encountered

  • Coding Agent (first attempt) timed out at 18 minutes with no files written — was mid-read phase. Resumed via a fresh agent invocation with the same detailed prompt. Second attempt (Opus model, 14 minutes) completed all 11 file changes.

  • cargo not on local Windows PATH — build had to be done remotely via SSH to the build server. The agent cargo check runs on Linux (non-Windows target) but catches type/import errors; Windows-specific conditional compilation (#[cfg(windows)]) is not checked but those paths are structurally unchanged.

  • Build triggered remotely after user corrected local build attempt — user noted builds should go to the build server, not local. Correct pattern: push to Gitea, SSH sudo /opt/gururmm/build-server.sh.

Configuration Changes

Committed to gururmm repo:

287d106 — fix: policy editor defaultHint reflects actual system default values

  • dashboard/src/pages/Policies.tsxPolicySectionEditorProps gains systemDefaults?: PolicyData; boolHint()/hints map computed from it; all 16 hardcoded defaultHint strings replaced with dynamic refs; PolicyDetailProps gains systemDefaultData?: PolicyData; both <PolicyDetail> call sites pass it

78b6831 — feat: wire agent policy end-to-end (metrics, tray, updates, watchdog)

  • agent/src/transport/mod.rsConfigUpdatePayload expanded to 4 nested section structs; WatchdogConfigUpdate gains services/processes fields
  • agent/src/main.rsAppState::effective_policy: RwLock<ConfigUpdatePayload> added
  • agent/src/transport/websocket.rsConfigUpdate handler implemented; metrics loop converted to sleep-based with per-iteration policy read
  • agent/src/metrics/mod.rscollect_with_flags() method added
  • agent/src/ipc.rsTrayPolicy::from_config_update() added; GetPolicy reads from AppState
  • agent/src/watchdog/pipe.rsWatchdogCommand::UpdateConfig variant added
  • agent/src/watchdog/monitor.rsWatchdogRuntimeConfig struct; UpdateConfig handler; enabled gate; configurable interval
  • server/src/policy/config_update.rs — NEW: AgentConfigUpdate mirror structs + policy_to_agent_config() helper
  • server/src/policy/mod.rspub mod config_update added
  • server/src/ws/mod.rsConfigUpdate sent after AuthAck; auto-update gated on policy
  • server/src/api/policies.rspush_config_update_to_affected() helper; called from assign_policy and remove_assignment

Infrastructure & Servers

  • GuruRMM server: 172.16.3.30:3001 — v0.3.1 rebuilt and deployed (build completed 00:00:31 UTC 2026-05-14)
  • Dashboard: /var/www/gururmm/dashboard/ — updated with dynamic policy hints
  • Agent crate: cargo check clean on Linux (server); Windows build pending (Pluto)

Commands & Outputs

# Remote server build (the correct build pattern)
ssh -i C:/Users/guru/.ssh/id_ed25519 guru@172.16.3.30 "sudo /opt/gururmm/build-server.sh 2>&1"
# → Compiling gururmm-server v0.3.1
# → warning: unused import: ... (68 warnings, all pre-existing)
# → Finished release profile in 4m 07s
# → === Server build complete: v0.3.1 ===

# Agent cargo check on build server (Linux target)
ssh ... guru@172.16.3.30 "cd /home/guru/gururmm/agent && /home/guru/.cargo/bin/cargo check 2>&1 | tail -5"
# → warning: struct PipeServer is never constructed (pre-existing)
# → Finished dev profile in 7.18s

# Deploy dashboard
cd D:/claudetools/projects/msp-tools/guru-rmm/dashboard && npm run build
scp -i C:/Users/guru/.ssh/id_ed25519 -r dist/* guru@172.16.3.30:/var/www/gururmm/dashboard/

Pending / Incomplete Tasks

  • Windows agent build pendingcargo check passed on Linux; full Windows cross-compile or Pluto build needed to confirm #[cfg(windows)] paths compile. Agent binary won't reflect policy wiring until built and deployed via build pipeline.

  • Network discovery feature — next work item, starting after this save.

  • Tray binary auto-update — still not implemented (carried over from prior session). Old tray binary on enrolled machines crashes ~30s after connecting to the NULL-DACL pipe.

  • Watchdog not installed on existing enrolled machinesensure_watchdog_running() is in the agent but existing machines won't trigger re-install until they receive a new agent binary.

Reference Information

  • Commits: 287d106 (defaultHint fix), 78b6831 (policy wiring)
  • Key source files:
    • agent/src/transport/mod.rsConfigUpdatePayload, MetricsConfigUpdate, TrayConfigUpdate, UpdatesConfigUpdate, WatchdogConfigUpdate
    • agent/src/main.rsAppState::effective_policy
    • agent/src/transport/websocket.rsConfigUpdate handler + dynamic metrics loop
    • agent/src/metrics/mod.rscollect_with_flags()
    • agent/src/ipc.rsTrayPolicy::from_config_update(), GetPolicy handler
    • agent/src/watchdog/monitor.rsWatchdogRuntimeConfig, UpdateConfig handler
    • server/src/policy/config_update.rspolicy_to_agent_config() (new file)
    • server/src/ws/mod.rs — post-AuthAck ConfigUpdate dispatch + auto-update gate
    • server/src/api/policies.rspush_config_update_to_affected()
  • Policy wiring status after this session:
    • Metrics: WIRED (interval + collect_* flags from policy)
    • Thresholds: WIRED server-side (unchanged — already worked)
    • Tray: WIRED (reads from AppState, no longer hardcoded)
    • Updates: WIRED (auto_update gate added server-side)
    • Watchdog: WIRED (UpdateConfig IPC + runtime config applied)