# 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 src/ config.ts # TOML configuration loading mqtt.ts # MQTT client, publishing, and HA discovery entities.ts # Entity management (state polling, command handling) ``` ### Entity System Systant uses a unified "entity" concept that combines state monitoring and command handling. Entity types: - **sensor**: Read-only numeric/string values (CPU usage, temperature, etc.) - **binary_sensor**: Read-only on/off states (service running, etc.) - **switch**: Controllable on/off with `on_command` and `off_command` - **light**: Same as switch, for display/monitor control - **button**: Press-only actions with `press_command` Each entity is defined in TOML with shell commands: ```toml [entities.cpu_usage] type = "sensor" state_command = "awk '/^cpu / {print int(($2+$4)*100/($2+$4+$5))}' /proc/stat" unit = "%" icon = "mdi:chip" name = "CPU Usage" [entities.headphones] type = "switch" state_command = "pactl get-default-sink | grep -q usb && echo ON || echo OFF" on_command = "pactl set-default-sink alsa_output.usb-..." off_command = "pactl set-default-sink alsa_output.pci-..." ``` ### MQTT Topics ``` systant/{hostname}/{entity_id}/state # State updates systant/{hostname}/{entity_id}/set # Commands (for switch/light) systant/{hostname}/{entity_id}/press # Button presses systant/{hostname}/availability # Online/offline status homeassistant/{type}/{hostname}_{id}/config # HA auto-discovery ``` ### 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) - **Config**: TOML (smol-toml) - **MQTT**: mqtt.js - **Packaging**: Nix flake with home-manager module ### NixOS/Home Manager Integration ```nix # flake.nix inputs inputs.systant.url = "git+ssh://..."; # Add overlay for pkgs.systant nixpkgs.overlays = [ inputs.systant.overlays.default ]; # Import home-manager module home-manager.sharedModules = [ inputs.systant.homeManagerModules.default ]; # In user config services.systant = { enable = true; settings = { /* TOML as Nix attrset */ }; # or: configFile = ./systant.toml; }; ``` ## Bun Conventions Default to using Bun instead of Node.js. - Use `bun ` instead of `node ` or `ts-node ` - Use `bun test` instead of `jest` or `vitest` - Use `bun build ` instead of `webpack` or `esbuild` - Use `bun install` instead of `npm install` or `yarn install` or `pnpm install` - Use `bun run