# UnPoller - Cursor AI Rules ## Project Overview UnPoller is a Go application that collects metrics from UniFi network controllers and exports them to various backends (InfluxDB, Prometheus, Loki, DataDog). The architecture is plugin-based with input and output plugins. ## Architecture ### Core Components - **main.go**: Entry point, loads plugins via blank imports - **pkg/poller/**: Core library providing input/output interfaces and plugin management - **pkg/inputunifi/**: Input plugin for UniFi controller data collection - **pkg/influxunifi/**: Output plugin for InfluxDB - **pkg/promunifi/**: Output plugin for Prometheus - **pkg/lokiunifi/**: Output plugin for Loki - **pkg/datadogunifi/**: Output plugin for DataDog - **pkg/webserver/**: Web server for health checks and metrics - **pkg/mysqlunifi/**: MySQL output plugin ### Plugin System - Plugins are loaded via blank imports (`_ "github.com/unpoller/unpoller/pkg/inputunifi"`) - Input plugins implement the `Input` interface from `pkg/poller/inputs.go` - Output plugins implement the `Output` interface from `pkg/poller/outputs.go` - The poller core automatically discovers and initializes imported plugins - Configuration is automatically unmarshaled from config files (TOML/JSON/YAML) and environment variables ## Code Style & Conventions ### Go Standards - Use Go 1.25.5+ features - Follow standard Go formatting (gofmt) - Use `golangci-lint` with the project's `.golangci.yaml` configuration - Enable linters: `nlreturn`, `revive`, `tagalign`, `testpackage`, `wsl_v5` - Use `//nolint:gci` for import ordering when needed ### Naming Conventions - Package names should be lowercase, single word - Exported types/functions use PascalCase - Unexported types/functions use camelCase - Constants use PascalCase with descriptive names - Error variables should be named `err` and checked immediately ### Error Handling - Always check errors, don't ignore them - Use `github.com/pkg/errors` for error wrapping when appropriate - Return errors from functions, don't log and continue - Use descriptive error messages ### Configuration - Configuration uses `golift.io/cnfg` and `golift.io/cnfgfile` - Environment variables use `UP_` prefix (defined in `ENVConfigPrefix`) - Support TOML (default), JSON, and YAML formats - Config structs should have tags: `json`, `toml`, `xml`, `yaml` ### Testing - Use `testpackage` linter - tests should be in separate `_test` packages - Use `github.com/stretchr/testify` for assertions - Integration tests use `integration_test_expectations.yaml` files ### Logging - Use structured logging via logger interfaces - Log levels: ERROR, WARN, INFO, DEBUG - Prefix log messages with `[LEVEL]` when using standard log package ## Dependencies ### Key Libraries - `github.com/unpoller/unifi/v5` - UniFi API client (local replace: `/Users/briangates/unifi`) - `golift.io/cnfg` - Configuration management - `golift.io/cnfgfile` - Config file parsing - `github.com/spf13/pflag` - CLI flag parsing - `github.com/gorilla/mux` - HTTP router - `github.com/prometheus/client_golang` - Prometheus metrics ### Output Backends - InfluxDB: `github.com/influxdata/influxdb1-client` and `github.com/influxdata/influxdb-client-go/v2` - Prometheus: `github.com/prometheus/client_golang` - Loki: Custom HTTP client - DataDog: `github.com/DataDog/datadog-go/v5` ## Project Structure ``` unpoller/ ├── main.go # Entry point ├── pkg/ │ ├── poller/ # Core plugin system │ ├── inputunifi/ # UniFi input plugin │ ├── influxunifi/ # InfluxDB output │ ├── promunifi/ # Prometheus output │ ├── lokiunifi/ # Loki output │ ├── datadogunifi/ # DataDog output │ ├── mysqlunifi/ # MySQL output │ └── webserver/ # Web server ├── examples/ # Configuration examples ├── init/ # Init scripts (systemd, docker, etc.) └── scripts/ # Build/deployment scripts ``` ## Common Patterns ### Adding a New Output Plugin 1. Create new package in `pkg/` (e.g., `pkg/myoutput/`) 2. Implement `Output` interface from `pkg/poller/outputs.go` 3. Register plugin in `init()` function 4. Add blank import to `main.go` 5. Add configuration struct with proper tags 6. Create README.md in package directory ### Adding a New Input Plugin 1. Create new package in `pkg/` (e.g., `pkg/myinput/`) 2. Implement `Input` interface from `pkg/poller/inputs.go` 3. Register plugin in `init()` function 4. Add blank import to `main.go` 5. Add configuration struct with proper tags ### Device Type Reporting - Device types: UAP, USG, USW, UDM, UBB, UCI, UXG, PDU - Each output plugin has device-specific reporting functions (e.g., `uap.go`, `usg.go`) - Follow existing patterns in `pkg/promunifi/` or `pkg/influxunifi/` ## Important Notes - The `pkg/poller` library is generic and has no knowledge of UniFi, Influx, Prometheus, etc. - It provides interfaces for any type of input/output plugin - Use `[]any` types for flexibility in the core library - Configuration is automatically loaded from files and environment variables - The application supports multiple UniFi controllers via configuration - Timezone can be set via `TZ` environment variable ## Build & Deployment - Uses GitHub Actions for CI/CD - Uses `goreleaser` for multi-platform builds - Supports: Linux, macOS, Windows, FreeBSD - Docker images built automatically - Homebrew formula for macOS - Configuration examples in `examples/` directory ## When Writing Code - Keep functions focused and single-purpose - Use interfaces for testability - Follow the existing plugin patterns - Document exported functions and types - Add appropriate error handling - Consider cross-platform compatibility (Windows, macOS, Linux, BSD) - Use context.Context for cancellable operations - Respect timeouts and deadlines