CONTRIBUTING.md
Rama main · réplica de lectura del repositorio original.
# Contributing to host-agent
Thanks for considering a contribution! This project is small and
focused — keeping it that way is a feature, so please read this before
opening anything substantial.
## What's in scope
- Bug fixes — particularly around new hardware (chassis profiles, GPU
models, drive types) that the agent silently mis-handles.
- New chassis profiles (`profiles/<model>.env`). These need IPMI sensor
names + the chassis' fan-floor behavior. See "Adding a chassis profile"
below.
- New sensor backends in `internal/sensors/`. If you have a non-NVIDIA
GPU or a non-IPMI fan-control path (e.g. Supermicro's `raw 0x30 0x70
0x66 …`), a clean implementation behind the existing interfaces is
welcome.
- Improvements to the control math in `internal/control/` — must come
with unit tests and a clear before/after.
- Docs improvements.
## What's *out* of scope
- **External Go dependencies.** The binary is intentionally
zero-dependency (no `go.sum` shipped, vendor-free build). Anything
that adds an `import "github.com/..."` needs a strong justification.
- **Lookup tables / piecewise fan curves.** The controller is dynamic
and derived-from-constants by design — temp ranges → continuous math
→ fan speed. No hardcoded `"if temp > 80 then fan = 60"` rules.
- **Replacing the IPMI/smartctl/nvidia-smi subprocess approach with a
library binding.** The subprocess pattern is what makes the image
trivially portable. Native bindings would pull in C deps, kill
CGO-disabled builds, and explode the image size.
- **Features that only make sense for a specific cloud or paid
service.** This project is hardware-and-Prometheus-shaped; please
keep it that way.
## Dev environment
You need Go 1.23+ and Docker. No other tooling.
```sh
git clone https://github.com/MattJackson/host-agent
cd host-agent
go test ./... # unit + e2e (no Docker needed)
docker build -t host-agent:dev . # full image build
```
Most logic lives in `internal/control/` (pure functions) and is exhaustively
unit-testable without any I/O. Subprocess sensors are mocked via the
`runner` interface — see `internal/runner/fake.go` and the testdata
under `internal/sensors/testdata/`.
## Code style
- `gofmt -s` (run by CI; PRs failing it are blocked).
- No external Go deps. Standard library only.
- Comments should explain *why* (a non-obvious constraint, a workaround
for vendor behavior, an invariant). Don't restate what the code does.
- Keep functions small and pure where possible. The "main loop" lives in
`internal/controller/`; everything it calls is testable in isolation.
## Adding a chassis profile
1. Determine the `dmidecode` product slug for your chassis:
```sh
sudo dmidecode -s system-product-name
# → "PowerEdge R730xd" → slug "r730xd"
```
The slug is what `detect_model` produces:
`lowercase(strip("PowerEdge "), then replace non-[a-z0-9] with _,
then strip trailing _)`.
2. Add `profiles/<slug>.env`. Minimum content is `MIN_FAN`:
```sh
: "${MIN_FAN:=15}" # the lowest PWM the BMC actually respects.
# Below this it either clamps to a safety RPM
# (Dell R7xx series) or obeys literally and
# stalls fans (Dell R4xx series). Test before
# picking a value.
```
3. (Optional) Add IPMI sensor mappings so the dashboard's
per-CPU/inlet/exhaust panels resolve. See `profiles/r730xd.env`:
```sh
: "${SENSOR_CPU1_NAME:=Temp}"
: "${SENSOR_CPU1_ID:=26}"
: "${SENSOR_CPU2_NAME:=Temp}"
: "${SENSOR_CPU2_ID:=27}"
: "${SENSOR_INLET_NAME:=Inlet Temp}"
: "${SENSOR_EXHAUST_NAME:=Exhaust Temp}"
```
The IDs come from `ipmi-sensors --output-sensor-state` on the actual
hardware. Run it once and grep for the temp sensors.
4. Test on real hardware: deploy a `host-agent:dev` build, watch
`docker logs host-agent` for the `[fan-controller] chassis=<slug>`
line and confirm the right profile loaded. Then watch one full
24-hour EWMA settling cycle before opening the PR.
5. PR description should include:
- chassis model + iDRAC/BMC firmware version
- `dmidecode -s system-product-name` output
- the fan-floor behavior you observed (which PWM values do what)
- a screenshot or paste of `/var/lib/host-agent/state/metrics.prom`
after the controller has been running for at least a few hours.
## Submitting PRs
- One change per PR. Don't bundle a profile addition with a controller
refactor.
- Reference any related issue in the description.
- Make sure `go test ./...` passes.
- CI runs `gofmt -s -l` and `go vet ./...`; PRs failing either are
blocked.
- Commit messages: imperative mood, ~70 char subject, blank line, body
if needed. No "Co-Authored-By: AI" trailers — keep authorship clean.
## Reporting bugs
See [SECURITY.md](SECURITY.md) for vulnerability reports (please don't
file public issues for those).
For functional bugs, open a GitHub issue with:
- which install method (compose, `docker run`, Unraid, install.sh)
- chassis (`dmidecode -s system-product-name`) + which profile loaded
(visible in container logs as `[fan-controller] chassis=…`)
- `docker logs host-agent --tail 200` output around the bad behavior
- contents of `/var/lib/host-agent/state/metrics.prom`
- expected vs actual behavior