feat: implement agent-os standards system and feature planning tools
- Split CODING_GUIDELINES.md into 19 indexed standards files under .claude/standards/ - 9 from CODING_GUIDELINES (conventions, powershell, security, api, git, gururmm) - 10 from session log tribal knowledge (syncro, ssh, gitea, python, client, gururmm) - Add .claude/standards/index.yml for cheap relevance-based lookup - Add /inject-standards command: load targeted standards per task instead of full guidelines - Add /shape-spec command: pre-implementation spec for GuruRMM features (plan.md, shape.md, references.md, standards.md) with mandatory out-of-scope gate - Add docs/tech-stack.md and docs/mission.md for ClaudeTools API - Add projects/msp-tools/guru-rmm/docs/tech-stack.md and mission.md for GuruRMM - Update CLAUDE.md commands table with /inject-standards and /shape-spec Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
84
.claude/standards/gururmm/build-pipeline.md
Normal file
84
.claude/standards/gururmm/build-pipeline.md
Normal file
@@ -0,0 +1,84 @@
|
||||
---
|
||||
name: build-pipeline
|
||||
description: Never run build-agents.sh manually; all builds go through the Gitea webhook pipeline (push to main)
|
||||
applies-to: gururmm
|
||||
---
|
||||
|
||||
# GuruRMM Build Pipeline
|
||||
|
||||
## The rule
|
||||
|
||||
Never run `build-agents.sh` manually via SSH unless recovering from a specific failure (e.g., a stale zombie lock file). All agent and server builds go through the Gitea webhook pipeline: push to `main` on `azcomputerguru/gururmm` triggers the build automatically.
|
||||
|
||||
Running the build script manually can create version conflicts, bypass the Authenticode signing step for Windows binaries, and leave the build log in an inconsistent state that causes false "build complete" notifications on the next real build.
|
||||
|
||||
## Normal build flow
|
||||
|
||||
```
|
||||
1. Edit code locally or on the server
|
||||
2. Commit changes
|
||||
3. Push to Gitea: git push origin main (or push to azcomputerguru/gururmm via Gitea remote)
|
||||
4. Gitea webhook fires POST to http://172.16.3.30:9000/webhook/build (HMAC-SHA256 signed)
|
||||
5. webhook-handler.py on Saturn spawns build-agents.sh (Linux) and triggers Pluto (Windows)
|
||||
6. Monitor: tail -f /var/log/gururmm-build.log on Saturn
|
||||
```
|
||||
|
||||
## Build lock
|
||||
|
||||
The build script uses `/var/run/gururmm-build.lock` to prevent concurrent builds. If a build crashes mid-run, the lock file is not cleaned up and the next webhook trigger will fail silently (webhook handler sees the lock and exits).
|
||||
|
||||
**Check for zombie lock before triggering any manual build:**
|
||||
|
||||
```bash
|
||||
# Check lock exists and get PID
|
||||
cat /var/run/gururmm-build.lock
|
||||
|
||||
# Check if PID is a zombie (os.kill returns 0 for zombies)
|
||||
# If PID no longer exists or is zombie, remove the lock
|
||||
sudo rm -f /var/run/gururmm-build.lock
|
||||
```
|
||||
|
||||
The build script should add a defensive `rm -f /var/run/gururmm-build.lock` at startup (pending improvement). Until that is added, manual lock cleanup before triggered builds is required after any build failure.
|
||||
|
||||
## Server build
|
||||
|
||||
The server binary is built separately from agents:
|
||||
|
||||
```bash
|
||||
# Trigger server build (as guru user on Saturn, or via SSH)
|
||||
ssh guru@172.16.3.30 "sudo /opt/gururmm/build-server.sh 2>&1"
|
||||
|
||||
# Monitor
|
||||
ssh guru@172.16.3.30 "tail -f /var/log/gururmm-build.log"
|
||||
```
|
||||
|
||||
The server build uses `SQLX_OFFLINE=true` (set in `/home/guru/.cargo/env`) to avoid the sqlx proc macro querying the live database during compilation.
|
||||
|
||||
## Service binary path
|
||||
|
||||
The deployed server binary is at `/opt/gururmm/gururmm-server` — this is what systemd's ExecStart points to. Do not deploy to `/usr/local/bin/gururmm-server` (that path has no service backing and has caused "deployed but not running" confusion in past sessions).
|
||||
|
||||
```bash
|
||||
# Correct deploy sequence
|
||||
sudo systemctl stop gururmm-server
|
||||
sudo cp /home/guru/gururmm/server/target/release/gururmm-server /opt/gururmm/gururmm-server
|
||||
sudo systemctl start gururmm-server
|
||||
```
|
||||
|
||||
## Windows agent build (Pluto)
|
||||
|
||||
Windows agent builds and Authenticode signing run on Pluto (172.16.3.36). The webhook handler triggers Pluto automatically. Signed binaries avoid the Windows SmartScreen warning that affected unsigned 0.6.2 builds.
|
||||
|
||||
The build generates all agent variants:
|
||||
- `gururmm-agent-linux-x86_64`
|
||||
- `gururmm-agent-linux-aarch64`
|
||||
- `gururmm-agent-windows-x86_64.exe`
|
||||
- `gururmm-agent-windows-x86.exe`
|
||||
- `gururmm-agent-macos-x86_64`
|
||||
- `gururmm-agent-macos-aarch64`
|
||||
|
||||
All are placed in `/var/www/gururmm/downloads/` on Saturn after build.
|
||||
|
||||
## Changelog generation
|
||||
|
||||
`build-agents.sh` calls `generate-changelog.sh` before the "Build complete" log line. This creates `changelogs/agent/v{VERSION}.md` and updates `changelogs/LATEST_AGENT.md` automatically on each build. Do not create changelog files manually.
|
||||
89
.claude/standards/gururmm/platform-parity.md
Normal file
89
.claude/standards/gururmm/platform-parity.md
Normal file
@@ -0,0 +1,89 @@
|
||||
---
|
||||
name: platform-parity
|
||||
description: All agent features must ship on Windows, Linux, and macOS; silent no-ops on one platform are bugs
|
||||
applies-to: gururmm
|
||||
---
|
||||
|
||||
# GuruRMM Agent — Platform Parity
|
||||
|
||||
All agent features that are not inherently platform-specific must ship on Windows, Linux, and macOS. A feature that silently no-ops on one platform is a gap, not a cross-platform implementation.
|
||||
|
||||
## The rule
|
||||
|
||||
> "Add feature X to the agent" means Windows + Linux + macOS. All three, in the same change.
|
||||
> No exceptions for convenience. If a real implementation is not feasible on a given platform,
|
||||
> add a working stub and a `// TODO(platform): <os> — <reason>` comment in the same commit.
|
||||
> A feature that silently no-ops on one platform without a stub and TODO is a bug, not a gap.
|
||||
|
||||
## cfg gating — choose the right target
|
||||
|
||||
| Condition | Attribute | When to use |
|
||||
|-----------|-----------|-------------|
|
||||
| Windows only | `#[cfg(windows)]` | Windows API (Win32, WMI, SCM, OpenSSH registry) |
|
||||
| Linux + macOS | `#[cfg(unix)]` | POSIX: nix crate, signals, `/proc`, `/sys`, sockets |
|
||||
| Linux only | `#[cfg(target_os = "linux")]` | `/sys/class/thermal`, systemd, procfs, D-Bus |
|
||||
| macOS only | `#[cfg(target_os = "macos")]` | CoreFoundation, IOKit, launchd, NSStatusBar |
|
||||
| Build flag | `#[cfg(feature = "native-service")]` | Service harness (Windows only in Cargo.toml) |
|
||||
|
||||
Never use `#[cfg(not(windows))]` as a proxy for "Linux + macOS works the same" without verifying the macOS codepath. Linux and macOS diverge on `/sys`, D-Bus, and GUI IPC.
|
||||
|
||||
## Current parity matrix (as of 2026-05-15)
|
||||
|
||||
| Feature | Windows | Linux | macOS |
|
||||
|---------|---------|-------|-------|
|
||||
| CPU / memory / disk / network metrics | [OK] | [OK] | [OK] |
|
||||
| Temperature via sysinfo | [OK] fallback | [WARN] empty if no hwmon | [WARN] empty if no sensors |
|
||||
| Temperature via LibreHardwareMonitor | [OK] primary | N/A | N/A |
|
||||
| Temperature via /sys/class/thermal | N/A | [GAP] not implemented | N/A |
|
||||
| User detection (logged-in user) | [OK] | [OK] nix crate | [OK] nix crate |
|
||||
| User idle time | [OK] GetLastInputInfo | [GAP] returns None | [GAP] returns None |
|
||||
| IPC / tray | [OK] named pipe + WinTray | [GAP] stub no-op | [GAP] stub no-op |
|
||||
| Watchdog (process monitor) | [OK] native-service | [GAP] stub no-op | [GAP] stub no-op |
|
||||
| Script execution | [OK] cmd / PowerShell | [OK] bash / sh | [OK] bash / sh |
|
||||
| Hardware inventory | [OK] WMI | [OK] /proc + lshw | [OK] system_profiler |
|
||||
| Auto-updater | [OK] full | [OK] simpler | [OK] simpler |
|
||||
| Checks (AV, updates, firewall) | [OK] full | [WARN] partial stub | [WARN] partial stub |
|
||||
| Network discovery | [OK] | [OK] | [OK] |
|
||||
|
||||
## Known gaps — priority order
|
||||
|
||||
**1. Linux temperature collection** (`agent/src/metrics/mod.rs`)
|
||||
- sysinfo `Components` returns empty on most Linux systems (requires kernel hwmon driver exposure).
|
||||
- Correct approach: read `/sys/class/thermal/thermal_zone*/temp` directly (always available on Linux).
|
||||
- Pattern:
|
||||
```rust
|
||||
#[cfg(target_os = "linux")]
|
||||
fn collect_temps_linux() -> (Option<f32>, Option<f32>, Vec<TemperatureReading>) {
|
||||
// read /sys/class/thermal/thermal_zone*/temp
|
||||
// parse millidegrees, classify by type label in /sys/class/thermal/thermal_zone*/type
|
||||
}
|
||||
```
|
||||
|
||||
**2. Linux / macOS user idle time** (`agent/src/metrics/mod.rs` — `get_user_idle_time()`)
|
||||
- Linux: use X11 `XScreenSaverQueryInfo` (display sessions) or parse `/proc/interrupts` delta (headless).
|
||||
- macOS: use `CGEventSourceSecondsSinceLastEventType` (IOKit, always available).
|
||||
- Stub is acceptable short-term; mark with `// TODO(platform): linux/macos idle time`.
|
||||
|
||||
**3. Watchdog on Linux / macOS** (`agent/src/watchdog/`)
|
||||
- Windows: Windows Service Control Manager restarts the agent.
|
||||
- Linux: systemd `Restart=on-failure` in the unit file is the correct equivalent — no in-process watchdog needed.
|
||||
- macOS: launchd `KeepAlive` key in the plist.
|
||||
- Document the OS-native mechanism in `build-agents.sh` / installer rather than porting the Rust watchdog.
|
||||
|
||||
**4. Checks on Linux / macOS** (`agent/src/checks.rs`)
|
||||
- Windows-specific checks (Windows Update pending, Windows Defender status, Windows Firewall) have no direct equivalents; that is expected.
|
||||
- Cross-platform checks (disk SMART, certificate expiry, open ports) should run on all platforms.
|
||||
- Add `// TODO(platform): linux/macos — <check name>` for each unimplemented cross-platform check.
|
||||
|
||||
## Cargo.toml dependency discipline
|
||||
|
||||
- Platform-specific crates go in `[target.'cfg(...)'.dependencies]`, never in `[dependencies]`.
|
||||
- Keep `lhm` (LibreHardwareMonitor) and `windows-service` under `cfg(windows)`.
|
||||
- Keep `nix` under `cfg(unix)`.
|
||||
- When adding a new crate, verify it compiles on all three targets before merging. Use the build server for Windows; CI covers Linux. macOS cross-compile via `--target aarch64-apple-darwin` on Linux (requires `osxcross` toolchain — see build-agents.sh TODO-MACOS).
|
||||
|
||||
## Additional notes from past sessions
|
||||
|
||||
**service.rs must mirror main.rs AppState**: On Windows, the agent runs as a Windows Service via a separate entry point in `service.rs` that constructs `AppState` independently. Any field added to `AppState` in `main.rs` must also be added to the `AppState` struct literal in `service.rs`. This has caused Windows-only build failures in the past (missing `agent_id` field). There is no shared constructor — both sites must be updated manually.
|
||||
|
||||
**sc.exe over Get-Service**: `Get-Service` silently fails to enumerate `GuruRMMAgent` even with the exact service name in some session contexts. `sc.exe queryex "GuruRMMAgent"` is reliable. All PS1-based service checks in agent code use `sc.exe query` equivalents.
|
||||
77
.claude/standards/gururmm/sqlx-migrations.md
Normal file
77
.claude/standards/gururmm/sqlx-migrations.md
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
name: sqlx-migrations
|
||||
description: Never manually pre-apply migrations without tracking rows; use IF NOT EXISTS; let the server apply its own migrations
|
||||
applies-to: gururmm
|
||||
---
|
||||
|
||||
# GuruRMM sqlx Migration Discipline
|
||||
|
||||
## The core rule
|
||||
|
||||
Never manually pre-apply migrations via psql without also recording the corresponding row in `_sqlx_migrations`. If the row is missing, the server binary will attempt to re-run the migration at startup and fail when it finds the table or column already exists.
|
||||
|
||||
## The correct workflow
|
||||
|
||||
Let the server binary apply its own migrations on startup:
|
||||
|
||||
```
|
||||
1. Write the SQL migration file (server/migrations/NNN_description.sql)
|
||||
2. Use ADD COLUMN IF NOT EXISTS / CREATE TABLE IF NOT EXISTS for idempotence
|
||||
3. Run cargo sqlx prepare (keeps .sqlx/ offline cache current)
|
||||
4. Commit the migration file + .sqlx/ changes
|
||||
5. Build the server binary (push to Gitea triggers build-server.sh)
|
||||
6. Deploy: stop → copy binary → start
|
||||
7. sqlx applies the migration on startup and records the checksum row
|
||||
```
|
||||
|
||||
Do not pre-apply the SQL with psql. Do not insert rows into `_sqlx_migrations` manually unless recovering from a specific failure.
|
||||
|
||||
## Why: the proc macro excludes pre-applied rows
|
||||
|
||||
When `DATABASE_URL` is set at compile time, `sqlx::migrate!()` queries `_sqlx_migrations` during compilation and embeds only the migrations not yet present in the DB. If you pre-apply migration 026 via psql and its row is in `_sqlx_migrations` before the build, the compiled binary will not contain migration 026 — then at runtime, finding a row for version 26 with no matching embedded migration causes a fatal startup error.
|
||||
|
||||
The fix: delete the pre-applied `_sqlx_migrations` row(s), rebuild with `SQLX_OFFLINE=true`, let the server apply them naturally.
|
||||
|
||||
## Write idempotent SQL
|
||||
|
||||
All migrations use `IF NOT EXISTS` or `IF EXISTS` forms:
|
||||
|
||||
```sql
|
||||
-- Tables
|
||||
CREATE TABLE IF NOT EXISTS policy_checks (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
...
|
||||
);
|
||||
|
||||
-- Columns
|
||||
ALTER TABLE agents ADD COLUMN IF NOT EXISTS update_channel TEXT
|
||||
CHECK (update_channel IN ('stable', 'beta'));
|
||||
```
|
||||
|
||||
This protects against the "table already exists" error if a migration is somehow applied twice, and allows the migration to be run safely during development resets.
|
||||
|
||||
## SQLX_OFFLINE build environment
|
||||
|
||||
`SQLX_OFFLINE=true` is set permanently in `/home/guru/.cargo/env` on Saturn. All cargo builds by the `guru` user use the `.sqlx/` offline cache rather than querying the live DB at compile time. This eliminates the proc macro/DB interaction entirely.
|
||||
|
||||
After any schema change that adds or modifies a `query!()` macro, re-run:
|
||||
```bash
|
||||
cd /home/guru/gururmm/server && cargo sqlx prepare
|
||||
git add server/.sqlx && git commit -m "build: update sqlx offline query cache"
|
||||
```
|
||||
|
||||
## Recovery from _sqlx_migrations mismatch
|
||||
|
||||
If the server fails to start with "migration N was previously applied but is missing in the resolved migrations":
|
||||
|
||||
```bash
|
||||
# Option 1: Delete the row (if the migration was manually applied and tables exist)
|
||||
PGPASSWORD=<pw> psql -h localhost -U gururmm -d gururmm \
|
||||
-c "DELETE FROM _sqlx_migrations WHERE version IN (N);"
|
||||
# Then rebuild so the binary embeds the migration
|
||||
|
||||
# Option 2: If checksum mismatch (binary embedded wrong content)
|
||||
# Fix the SQL file, rerun cargo sqlx prepare, rebuild, deploy
|
||||
```
|
||||
|
||||
Never delete `_sqlx_migrations` rows for migrations that the current binary does NOT embed — those rows protect against re-running already-applied migrations.
|
||||
Reference in New Issue
Block a user