diff --git a/.cursorrules b/.cursorrules new file mode 100644 index 00000000..b2dc5f73 --- /dev/null +++ b/.cursorrules @@ -0,0 +1,148 @@ +# 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 diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..c6a2c422 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,127 @@ +# UnPoller - GitHub Copilot Instructions + +## Project Overview + +UnPoller is a Go application that collects metrics and events from UniFi network controllers and exports them to monitoring backends (InfluxDB, Prometheus, Loki, DataDog, MySQL). The application uses a plugin-based architecture with a generic core. + +## Architecture + +### Plugin System +- **Core Library** (`pkg/poller/`): Generic plugin system with input/output interfaces +- **Input Plugins**: Collect data (e.g., `pkg/inputunifi/`) +- **Output Plugins**: Export data to backends (e.g., `pkg/influxunifi/`, `pkg/promunifi/`) +- **Plugin Discovery**: Automatic via blank imports in `main.go` + +### Key Interfaces + +**Input Interface**: +```go +type Input interface { + GetMetrics() (*Metrics, error) + GetEvents() (*Events, error) +} +``` + +**Output Interface**: +```go +type Output interface { + WriteMetrics(*Metrics) error + WriteEvents(*Events) error +} +``` + +## Code Style + +### Go Conventions +- Go 1.25.5+ +- Use `gofmt` formatting +- Follow `.golangci.yaml` linting rules +- Enabled linters: `nlreturn`, `revive`, `tagalign`, `testpackage`, `wsl_v5` + +### Naming +- Packages: `lowercase` +- Exported: `PascalCase` +- Unexported: `camelCase` +- Errors: Always `err`, check immediately + +### Error Handling +```go +if err != nil { + return fmt.Errorf("context: %w", err) +} +``` + +### Configuration +All config structs must include format tags: +```go +type Config struct { + Field string `json:"field" toml:"field" xml:"field" yaml:"field"` +} +``` + +Environment variables use `UP_` prefix. + +## Common Patterns + +### Plugin Registration +```go +func init() { + poller.RegisterInput(&Input{}) + // or + poller.RegisterOutput(&Output{}) +} +``` + +### Device Type Reporting +Each output plugin has device-specific functions: +- `UAP()` - Access Points +- `USG()` - Security Gateways +- `USW()` - Switches +- `UDM()` - Dream Machines +- `UXG()` - Next-Gen Gateways +- `UBB()` - Building Bridges +- `UCI()` - Industrial devices +- `PDU()` - Power Distribution Units + +### Adding New Plugin +1. Create package in `pkg/` +2. Implement `Input` or `Output` interface +3. Register in `init()` +4. Add blank import to `main.go` +5. Add config struct with tags +6. Create `README.md` + +## Dependencies + +### Core +- `github.com/unpoller/unifi/v5` - UniFi API (local: `/Users/briangates/unifi`) +- `golift.io/cnfg` - Configuration +- `golift.io/cnfgfile` - Config parsing +- `github.com/spf13/pflag` - CLI flags + +### Outputs +- InfluxDB: `github.com/influxdata/influxdb1-client`, `github.com/influxdata/influxdb-client-go/v2` +- Prometheus: `github.com/prometheus/client_golang` +- DataDog: `github.com/DataDog/datadog-go/v5` +- Loki: Custom HTTP client + +## Important Notes + +- `pkg/poller` core is generic - no UniFi/backend knowledge +- Support Windows, macOS, Linux, BSD +- Configuration: TOML (default), JSON, YAML, env vars +- Always check and return errors +- Use `context.Context` for cancellable operations +- Tests in `_test` packages +- Use `github.com/stretchr/testify` for assertions + +## When Writing Code + +1. Follow existing plugin patterns +2. Keep core library generic +3. Check all errors +4. Document exported functions +5. Write tests in `_test` packages +6. Consider cross-platform compatibility +7. Use structured logging +8. Respect timeouts and deadlines diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 00000000..20f001db --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,270 @@ +# UnPoller - AI Agent Context + +This file provides comprehensive context for AI coding assistants working on the UnPoller project. + +## Project Identity + +**Name**: UnPoller (UniFi Poller) +**Language**: Go 1.25.5+ +**Purpose**: Collect metrics and events from UniFi network controllers and export to monitoring backends +**Repository**: https://github.com/unpoller/unpoller +**Documentation**: https://unpoller.com + +## Architecture Overview + +UnPoller uses a **plugin-based architecture** with a generic core that provides input/output interfaces. The core library (`pkg/poller`) has no knowledge of UniFi or specific backends - it's a generic plugin system. + +### Core Components + +1. **Plugin System** (`pkg/poller/`): + - Generic input/output interfaces + - Automatic plugin discovery via blank imports + - Configuration management (TOML/JSON/YAML + env vars) + - Metrics and events aggregation + +2. **Input Plugins**: + - `pkg/inputunifi/` - Collects data from UniFi controllers + +3. **Output Plugins**: + - `pkg/influxunifi/` - InfluxDB (v1 and v2) + - `pkg/promunifi/` - Prometheus + - `pkg/lokiunifi/` - Loki + - `pkg/datadogunifi/` - DataDog + - `pkg/mysqlunifi/` - MySQL + +4. **Web Server** (`pkg/webserver/`): + - Health checks + - Metrics endpoint + - Plugin information + +## Code Organization + +### Directory Structure + +``` +unpoller/ +├── main.go # Entry point, loads plugins +├── pkg/ +│ ├── poller/ # Core plugin system (generic) +│ │ ├── config.go # Configuration structures +│ │ ├── inputs.go # Input plugin interface +│ │ ├── outputs.go # Output plugin interface +│ │ ├── start.go # Application startup +│ │ └── commands.go # CLI commands +│ ├── 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 scripts +``` + +### Plugin Interface Pattern + +**Input Plugin**: +```go +type Input interface { + GetMetrics() (*Metrics, error) + GetEvents() (*Events, error) +} +``` + +**Output Plugin**: +```go +type Output interface { + WriteMetrics(*Metrics) error + WriteEvents(*Events) error +} +``` + +**Registration**: +```go +func init() { + poller.RegisterInput(&MyInput{}) + // or + poller.RegisterOutput(&MyOutput{}) +} +``` + +## Coding Standards + +### Go Style + +- **Formatting**: Standard `gofmt` +- **Linting**: `.golangci.yaml` configuration + - Enabled: `nlreturn`, `revive`, `tagalign`, `testpackage`, `wsl_v5` +- **Imports**: Use `//nolint:gci` when import ordering needs exception + +### Naming Conventions + +- Packages: `lowercase`, single word +- Exported: `PascalCase` +- Unexported: `camelCase` +- Constants: `PascalCase` with descriptive names +- Errors: Always `err`, checked immediately + +### Error Handling + +```go +// ✅ Good +if err != nil { + return fmt.Errorf("context: %w", err) +} + +// ❌ Bad +_ = functionThatReturnsError() +``` + +### Configuration Pattern + +All configuration structs must include: +```go +type Config struct { + Field string `json:"field" toml:"field" xml:"field" yaml:"field"` +} +``` + +- Environment variables use `UP_` prefix (defined in `ENVConfigPrefix`) +- Automatic unmarshaling via `golift.io/cnfg` and `golift.io/cnfgfile` + +### Testing + +- Tests in `_test` packages (enforced by `testpackage` linter) +- Use `github.com/stretchr/testify` for assertions +- Integration tests: `integration_test.go` with `integration_test_expectations.yaml` + +## Key Dependencies + +### Core Libraries +- `github.com/unpoller/unifi/v5` - UniFi API client (local: `/Users/briangates/unifi`) +- `golift.io/cnfg` - Configuration management +- `golift.io/cnfgfile` - Config file parsing +- `github.com/spf13/pflag` - CLI flags +- `github.com/gorilla/mux` - HTTP router + +### Output Backends +- **InfluxDB**: `github.com/influxdata/influxdb1-client` (v1), `github.com/influxdata/influxdb-client-go/v2` (v2) +- **Prometheus**: `github.com/prometheus/client_golang` +- **DataDog**: `github.com/DataDog/datadog-go/v5` +- **Loki**: Custom HTTP implementation + +## Data Structures + +### Metrics +```go +type Metrics struct { + TS time.Time + Sites []any + Clients []any + SitesDPI []any + ClientsDPI []any + Devices []any + RogueAPs []any + SpeedTests []any + CountryTraffic []any +} +``` + +### Events +```go +type Events struct { + Logs []any +} +``` + +### Device Types +UniFi devices supported: +- **UAP** - Access Points (`uap.go`) +- **USG** - Security Gateways (`usg.go`) +- **USW** - Switches (`usw.go`) +- **UDM** - Dream Machines (`udm.go`) +- **UXG** - Next-Gen Gateways (`uxg.go`) +- **UBB** - Building Bridges (`ubb.go`) +- **UCI** - Industrial devices (`uci.go`) +- **PDU** - Power Distribution Units (`pdu.go`) + +Each output plugin has device-specific reporting functions following the pattern: +```go +func (r *Report) UAP(uaps []*unifi.UAP) { + // Convert and export UAP data +} +``` + +## Common Development Tasks + +### Adding a New Output Plugin + +1. Create package in `pkg/myoutput/` +2. Implement `Output` interface +3. Register in `init()` function +4. Add blank import to `main.go`: `_ "github.com/unpoller/unpoller/pkg/myoutput"` +5. Add configuration struct with tags +6. Create `README.md` with usage examples + +### Adding Device Type Support + +1. Add device-specific file (e.g., `mydevice.go`) +2. Follow pattern from existing device files +3. Convert UniFi device struct to output format +4. Handle errors appropriately + +### Modifying Configuration + +1. Add fields to appropriate config struct +2. Include all format tags: `json`, `toml`, `xml`, `yaml` +3. Update example configs in `examples/` +4. Document in package `README.md` + +## Build & Deployment + +- **CI/CD**: GitHub Actions (`.github/workflows/`) +- **Build**: `goreleaser` (`.goreleaser.yaml`) +- **Platforms**: Linux, macOS, Windows, FreeBSD +- **Docker**: Auto-built to `ghcr.io` +- **Packages**: Debian, RedHat via goreleaser +- **Homebrew**: Formula for macOS + +## Important Constraints + +1. **Generic Core**: `pkg/poller` must remain generic - no UniFi/backend knowledge +2. **Cross-Platform**: Support Windows, macOS, Linux, BSD +3. **Configuration**: Support TOML (default), JSON, YAML, and environment variables +4. **Error Handling**: Always check and return errors +5. **Context**: Use `context.Context` for cancellable operations +6. **Timeouts**: Respect timeouts and deadlines +7. **Logging**: Use structured logging via logger interfaces + +## Configuration Examples + +**File Formats**: See `examples/up.conf.example`, `examples/up.json.example`, `examples/up.yaml.example` + +**Environment Variables**: +```bash +UP_INFLUX_URL=http://localhost:8086 +UP_UNIFI_DEFAULT_USER=admin +UP_UNIFI_DEFAULT_PASS=password +UP_POLLER_DEBUG=true +UP_POLLER_INTERVAL=30s +``` + +## When Writing Code + +1. **Follow Existing Patterns**: Look at similar plugins for examples +2. **Keep Core Generic**: Don't add UniFi/backend-specific code to `pkg/poller` +3. **Error Handling**: Check all errors, return descriptive messages +4. **Documentation**: Document exported functions and types +5. **Testing**: Write tests in `_test` packages +6. **Cross-Platform**: Test on multiple platforms when possible +7. **Performance**: Consider polling intervals and data volume +8. **Security**: Don't log passwords or sensitive data + +## Resources + +- **Documentation**: https://unpoller.com +- **UniFi Library**: https://github.com/unpoller/unifi +- **Grafana Dashboards**: https://grafana.com/dashboards?search=unifi-poller +- **Discord**: https://golift.io/discord (#unpoller channel) diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 00000000..88564033 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,241 @@ +# UnPoller - Claude Code Context + +## Project Overview + +UnPoller is a Go application that collects metrics and events from UniFi network controllers and exports them to various time-series databases and monitoring systems. The application uses a plugin-based architecture where input plugins collect data and output plugins export it to different backends. + +## Architecture + +### Core Design Principles + +1. **Plugin-Based Architecture**: The core `pkg/poller` library is generic and provides interfaces for input and output plugins. It has no knowledge of UniFi, InfluxDB, Prometheus, etc. + +2. **Automatic Plugin Discovery**: Plugins are loaded via blank imports in `main.go`. The poller automatically discovers and initializes all imported plugins. + +3. **Flexible Configuration**: Supports TOML (default), JSON, and YAML configuration files, plus environment variables with `UP_` prefix. + +4. **Multiple Backends**: Supports InfluxDB, Prometheus, Loki, DataDog, and MySQL as output backends. + +### Package Structure + +``` +pkg/ +├── poller/ # Core plugin system - generic, no UniFi/backend knowledge +├── inputunifi/ # UniFi controller input plugin +├── influxunifi/ # InfluxDB output plugin +├── promunifi/ # Prometheus output plugin +├── lokiunifi/ # Loki output plugin +├── datadogunifi/ # DataDog output plugin +├── mysqlunifi/ # MySQL output plugin +└── webserver/ # Web server for health checks and metrics +``` + +### Key Interfaces + +**Input Interface** (`pkg/poller/inputs.go`): +- `GetMetrics()` - Returns aggregated metrics +- `GetEvents()` - Returns events/logs +- Plugins must implement this to provide data + +**Output Interface** (`pkg/poller/outputs.go`): +- `WriteMetrics()` - Writes metrics to backend +- `WriteEvents()` - Writes events to backend +- Plugins must implement this to export data + +## Code Style Guidelines + +### Go Conventions + +- **Go Version**: 1.25.5+ +- **Formatting**: Use `gofmt` standard formatting +- **Linting**: Follow `.golangci.yaml` configuration + - Enabled: `nlreturn`, `revive`, `tagalign`, `testpackage`, `wsl_v5` + - Use `//nolint:gci` for import ordering exceptions + +### Naming + +- **Packages**: Lowercase, single word +- **Exported**: PascalCase (types, functions, constants) +- **Unexported**: camelCase +- **Errors**: Always named `err`, checked immediately + +### Error Handling + +```go +// Good: Check and return errors +if err != nil { + return fmt.Errorf("operation failed: %w", err) +} + +// Bad: Ignoring errors +_ = someFunction() +``` + +### Configuration Pattern + +```go +type Config struct { + URL string `json:"url" toml:"url" yaml:"url"` + Interval string `json:"interval" toml:"interval" yaml:"interval"` + Timeout string `json:"timeout" toml:"timeout" yaml:"timeout"` +} +``` + +- Always include `json`, `toml`, `xml`, `yaml` tags +- Environment variables use `UP_` prefix automatically +- Use `golift.io/cnfg` for configuration management + +### Testing + +- Tests in separate `_test` packages (enforced by `testpackage` linter) +- Use `github.com/stretchr/testify` for assertions +- Integration tests use `integration_test_expectations.yaml` + +## Common Tasks + +### Adding a New Output Plugin + +1. **Create Package Structure**: + ```go + package myoutput + + import ( + "github.com/unpoller/unpoller/pkg/poller" + ) + + type Output struct { + // Configuration fields + } + + func (o *Output) WriteMetrics(m *poller.Metrics) error { + // Implementation + } + + func (o *Output) WriteEvents(e *poller.Events) error { + // Implementation + } + + func init() { + poller.RegisterOutput(&Output{}) + } + ``` + +2. **Add Blank Import** to `main.go`: + ```go + _ "github.com/unpoller/unpoller/pkg/myoutput" + ``` + +3. **Add Configuration** to config structs with proper tags + +4. **Create README.md** documenting usage + +### Adding Device Type Support + +Each output plugin has device-specific files: +- `uap.go` - Access Points +- `usg.go` - Security Gateways +- `usw.go` - Switches +- `udm.go` - Dream Machines +- `uxg.go` - Next-Gen Gateways +- `ubb.go` - Building Bridges +- `uci.go` - Industrial devices +- `pdu.go` - Power Distribution Units + +Follow the pattern in existing files: +```go +func (r *Report) UAP(uaps []*unifi.UAP) { + // Convert UniFi device data to output format +} +``` + +### Working with UniFi Data + +- UniFi library: `github.com/unpoller/unifi/v5` (local replace at `/Users/briangates/unifi`) +- Device types come from the UniFi controller API +- Data includes: sites, clients, devices, DPI data, speed tests, country traffic +- Events include: system logs, alarms, IDS events, anomalies + +## Dependencies + +### Core +- `github.com/unpoller/unifi/v5` - UniFi API client +- `golift.io/cnfg` - Configuration management +- `golift.io/cnfgfile` - Config file parsing +- `github.com/spf13/pflag` - CLI flags + +### Output Backends +- InfluxDB: `github.com/influxdata/influxdb1-client` (v1) and `github.com/influxdata/influxdb-client-go/v2` (v2) +- Prometheus: `github.com/prometheus/client_golang` +- DataDog: `github.com/DataDog/datadog-go/v5` +- Loki: Custom HTTP client implementation + +## Important Patterns + +### Plugin Registration +```go +func init() { + poller.RegisterInput(&Input{}) + // or + poller.RegisterOutput(&Output{}) +} +``` + +### Configuration Loading +Configuration is automatically loaded from: +1. Config file (TOML/JSON/YAML) - path specified via `--config` flag or defaults +2. Environment variables with `UP_` prefix +3. CLI flags + +### Metrics Structure +```go +type Metrics struct { + TS time.Time + Sites []any + Clients []any + SitesDPI []any + ClientsDPI []any + Devices []any + RogueAPs []any + SpeedTests []any + CountryTraffic []any +} +``` + +### Events Structure +```go +type Events struct { + Logs []any +} +``` + +## Build & Deployment + +- **CI/CD**: GitHub Actions +- **Build Tool**: `goreleaser` for multi-platform builds +- **Platforms**: Linux, macOS, Windows, FreeBSD +- **Docker**: Images built automatically to `ghcr.io` +- **Homebrew**: Formula for macOS users +- **Packages**: Debian, RedHat packages via goreleaser + +## When Writing Code + +1. **Keep it Generic**: The `pkg/poller` core should remain generic +2. **Follow Patterns**: Look at existing plugins for examples +3. **Error Handling**: Always check and return errors +4. **Cross-Platform**: Consider Windows, macOS, Linux, BSD differences +5. **Context Usage**: Use `context.Context` for cancellable operations +6. **Timeouts**: Respect timeouts and deadlines +7. **Documentation**: Document exported functions and types +8. **Testing**: Write tests in separate `_test` packages + +## Configuration Examples + +Configuration files support multiple formats. See `examples/` directory: +- `up.conf.example` - TOML format +- `up.json.example` - JSON format +- `up.yaml.example` - YAML format + +Environment variables use `UP_` prefix: +- `UP_INFLUX_URL=http://localhost:8086` +- `UP_UNIFI_DEFAULT_USER=admin` +- `UP_POLLER_DEBUG=true`