Fan control daemon for TrueNAS
Project description
truefan
Fan control daemon for TrueNAS SCALE systems on Supermicro X11 boards. Reads temperatures from IPMI, SMART, NVMe, and lm-sensors, then adjusts fan duty cycles via IPMI raw commands to keep things cool with minimal noise.
Features
- Auto-detection of sensors and fans — classifies by type (cpu, drive, nvme, ambient, other) and applies per-class interpolation curves.
- Self-calibrating — learns each fan's setpoint table by ramping duty down and recording RPMs. Can be recalibrated as fans age or collect dust.
- Failsafe — fans go to 100% on crash, total sensor class failure, or stalled fan. The watchdog parent restarts the daemon automatically.
- Netdata metrics — per-sensor thermal load, per-zone duty, per-fan target RPM, and daemon restart count via statsd.
Requirements
- Python 3.11+
ipmitoolsmartctl(smartmontools) — for SATA/SAS drive tempsnvme-cli— for NVMe tempslm-sensors— for kernel-exposed sensors- Supermicro X11 motherboard with IPMI
Install
TrueNAS SCALE doesn't ship ensurepip, so create the venv without it and
bootstrap pip manually:
python3 -m venv --without-pip /mnt/pool1/venvs/truefan
source /mnt/pool1/venvs/truefan/bin/activate
curl -sS https://bootstrap.pypa.io/get-pip.py | python3
pip install truefan
Put the venv on a pool — the boot drive is wiped on OS updates.
Quick start
# Detect sensors, calibrate fans, write config
sudo truefan init
# Start the daemon
sudo truefan run
# Show detected sensors and current readings
truefan sensors
# Re-calibrate after cleaning or replacing fans
sudo truefan recalibrate
# Reload config without restarting
sudo truefan reload
Configuration
truefan init generates a truefan.toml with sensible defaults. Example:
poll_interval_seconds = 15
spindown_window_seconds = 180
[curves.drive]
temp_low = 30
temp_high = 45
duty_low = 25
duty_high = 100
fan_zones = ["peripheral"]
# Per-sensor overrides for components that run hotter than their class
[curves.sensor.lmsensors-mlx5-pci-0200-sensor0]
temp_low = 60
temp_high = 95
# Learned via calibration — duty % = expected RPM
[fans.FAN1]
zone = "cpu"
[fans.FAN1.setpoints]
25 = 320
30 = 450
40 = 620
50 = 780
100 = 1500
Use --config PATH with any command to specify an alternate config location.
Running on boot
TrueNAS SCALE's Init/Shutdown Scripts (under System > Advanced) run
commands at boot and shutdown. Use tmux to run the daemon in a detachable
session you can attach to later for debugging.
Add a script (Type: Command, When: Post Init):
tmux new-session -d -s truefan '/mnt/pool1/venvs/truefan/bin/truefan run 2>&1 | tee /var/log/truefan.log'
To check on the daemon: tmux attach -t truefan. Detach with Ctrl-b d.
How it works
Each sensor class has a temperature-to-duty curve. Between temp_low and
temp_high, duty is linearly interpolated; hardware-reported thermal limits
override temp_high when available. The hottest sensor in each fan zone
sets the duty. A spindown window prevents rapid cycling.
If a fan stalls, the zone goes to 100% and the lowest setpoint is removed so the minimum duty rises going forward.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file truefan-0.9.0.tar.gz.
File metadata
- Download URL: truefan-0.9.0.tar.gz
- Upload date:
- Size: 52.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7a2c271a4439b2edfb23fc8b072e0477b35d3a80f98a72b66fb9b46f56911e93
|
|
| MD5 |
e1ca74cdc13e8f3f8d65a6d708e53bc8
|
|
| BLAKE2b-256 |
e8c9a63f72d2536f54c3844c6eb6fd38402817a10640d763dfa8b33180f25744
|
Provenance
The following attestation bundles were made for truefan-0.9.0.tar.gz:
Publisher:
ci.yml on zvea/truefan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
truefan-0.9.0.tar.gz -
Subject digest:
7a2c271a4439b2edfb23fc8b072e0477b35d3a80f98a72b66fb9b46f56911e93 - Sigstore transparency entry: 1191328077
- Sigstore integration time:
-
Permalink:
zvea/truefan@3a0e0d050f0a423c6cee22b065fc0bc6674c737a -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/zvea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@3a0e0d050f0a423c6cee22b065fc0bc6674c737a -
Trigger Event:
push
-
Statement type:
File details
Details for the file truefan-0.9.0-py3-none-any.whl.
File metadata
- Download URL: truefan-0.9.0-py3-none-any.whl
- Upload date:
- Size: 30.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8d6a684f7e9b0c5b0464ba8977080abb9044c9897dcf43e00ae938fa0d833c7e
|
|
| MD5 |
c269a10f4c9e74b9146dea6b7f692eb7
|
|
| BLAKE2b-256 |
1a0a93a972c551ec519b4e2b9e5183f0bfb3692b58daec9335bd5efd7317f0ce
|
Provenance
The following attestation bundles were made for truefan-0.9.0-py3-none-any.whl:
Publisher:
ci.yml on zvea/truefan
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
truefan-0.9.0-py3-none-any.whl -
Subject digest:
8d6a684f7e9b0c5b0464ba8977080abb9044c9897dcf43e00ae938fa0d833c7e - Sigstore transparency entry: 1191328081
- Sigstore integration time:
-
Permalink:
zvea/truefan@3a0e0d050f0a423c6cee22b065fc0bc6674c737a -
Branch / Tag:
refs/tags/v0.9.0 - Owner: https://github.com/zvea
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@3a0e0d050f0a423c6cee22b065fc0bc6674c737a -
Trigger Event:
push
-
Statement type: