A lightweight system monitoring agent that: - Collects metrics via configurable shell commands - Publishes to MQTT with Home Assistant auto-discovery - Supports entity types: sensor, binary_sensor, light, switch, button - Responds to commands over MQTT for controllable entities Architecture: - src/config.ts: TOML config loading and validation - src/mqtt.ts: MQTT client with HA discovery - src/entities.ts: Entity state polling and command handling - index.ts: CLI entry point (run, check, once commands) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
121 lines
3.7 KiB
Markdown
121 lines
3.7 KiB
Markdown
# Systant
|
|
|
|
A system monitoring agent that collects metrics, monitors services, and reports to MQTT/Home Assistant; and responds to events over MQTT to trigger commands or other behavior.
|
|
|
|
## Project Overview
|
|
|
|
Systant is a lightweight CLI tool written in Bun/TypeScript that:
|
|
- Collects system metrics (CPU, memory, disk, network)
|
|
- Monitors service health
|
|
- Publishes data to MQTT brokers
|
|
- Supports Home Assistant auto-discovery
|
|
- **Listens for MQTT commands** to trigger actions (run scripts, restart services, etc.)
|
|
- **Responds to events** with configurable handlers
|
|
- Runs as a daemon or one-shot command
|
|
|
|
### Architecture
|
|
|
|
```
|
|
index.ts # CLI entry point (yargs)
|
|
src/
|
|
commands/ # CLI command handlers
|
|
metrics/ # System metric collectors
|
|
mqtt/ # MQTT client and publishing
|
|
events/ # MQTT event listeners and handlers
|
|
actions/ # Executable actions (shell, service, notify)
|
|
ha/ # Home Assistant discovery
|
|
config/ # Configuration loading
|
|
```
|
|
|
|
### Event/Command System
|
|
|
|
Systant subscribes to MQTT topics and executes configured actions:
|
|
|
|
```
|
|
Topic: systant/{hostname}/command/{action}
|
|
Payload: { "args": [...], "timeout": 30 }
|
|
|
|
Topic: systant/{hostname}/event/{event_name}
|
|
Payload: { ... event data ... }
|
|
```
|
|
|
|
Actions are sandboxed and configurable via allowlists in the config file. Security is critical - never execute arbitrary commands without validation.
|
|
|
|
### Key Design Decisions
|
|
|
|
- **Single binary**: Compiles to standalone executable via `bun build --compile`
|
|
- **No external services**: Uses Bun built-ins (sqlite, file, etc.)
|
|
- **Config-driven**: TOML configuration for flexibility
|
|
- **Typed throughout**: Full TypeScript with strict mode
|
|
|
|
## Tech Stack
|
|
|
|
- **Runtime**: Bun (not Node.js)
|
|
- **CLI**: yargs
|
|
- **Config**: TOML
|
|
- **MQTT**: mqtt.js or Bun-native when available
|
|
- **Package**: Nix flake for reproducible builds
|
|
|
|
## Bun Conventions
|
|
|
|
Default to using Bun instead of Node.js.
|
|
|
|
- Use `bun <file>` instead of `node <file>` or `ts-node <file>`
|
|
- Use `bun test` instead of `jest` or `vitest`
|
|
- Use `bun build <file.html|file.ts|file.css>` instead of `webpack` or `esbuild`
|
|
- Use `bun install` instead of `npm install` or `yarn install` or `pnpm install`
|
|
- Use `bun run <script>` instead of `npm run <script>`
|
|
- Use `bunx <package> <command>` instead of `npx <package> <command>`
|
|
- Bun automatically loads .env, so don't use dotenv.
|
|
|
|
### Bun APIs
|
|
|
|
- `Bun.serve()` for HTTP/WebSocket servers
|
|
- `bun:sqlite` for SQLite (not better-sqlite3)
|
|
- `Bun.file()` for file I/O (not node:fs readFile/writeFile)
|
|
- `Bun.$\`cmd\`` for shell commands (not execa)
|
|
- Native `WebSocket` (not ws)
|
|
|
|
### Testing
|
|
|
|
```ts
|
|
import { test, expect, describe, beforeEach } from "bun:test";
|
|
|
|
describe("MetricCollector", () => {
|
|
test("collects CPU metrics", async () => {
|
|
const metrics = await collectCPU();
|
|
expect(metrics.usage).toBeGreaterThanOrEqual(0);
|
|
});
|
|
});
|
|
```
|
|
|
|
Run tests: `bun test`
|
|
Run specific: `bun test src/metrics`
|
|
Watch mode: `bun test --watch`
|
|
|
|
## Code Style
|
|
|
|
- Prefer `async/await` over callbacks
|
|
- Use explicit return types on public functions
|
|
- Prefer `interface` over `type` for object shapes
|
|
- Use `const` by default, `let` only when reassignment needed
|
|
- No classes unless state encapsulation is genuinely needed
|
|
- Prefer pure functions and composition
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
bun run start # Run in development
|
|
bun run dist # Build standalone binary
|
|
bun test # Run tests
|
|
bun test --watch # Watch mode
|
|
```
|
|
|
|
## Planning Protocol
|
|
|
|
When implementing features:
|
|
1. Discuss the approach before writing code
|
|
2. Start with types/interfaces
|
|
3. Write tests alongside implementation
|
|
4. Keep PRs focused and small
|