This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
这是一个跨平台的 IPMI 风扇控制器,用于监控服务器 CPU 温度并根据预定义的温度区间自动调整风扇转速。支持 Windows 和 Linux 平台。
- 循环控制模式 (
fancontroller.py): 程序内部循环监控,适合长期后台运行 - 单次执行模式 (
fancontroller_once.py): 执行一次后退出,适合外部调度工具(cron、systemd timer)定时调用
- fancontroller.py: 循环模式入口,负责配置加载、日志初始化和多线程管理
- fancontroller_once.py: 单次执行模式入口,执行一次后退出
- base_controller.py:
IPMIFanController基类,定义通用的 IPMI 命令执行逻辑adjust_fans_once(): 核心方法,执行一次温度检测和风扇调整process_server_loop(): 循环模式,持续调用adjust_fans_once()process_server_once(): 单次模式,调用一次adjust_fans_once()后退出
- dell730_controller.py:
Dell730FanController子类,实现 Dell 730 系列服务器的具体控制逻辑_initialize_dell730(): Dell 730 特定初始化(禁用 PCIe 散热响应、设置手动模式)start_fan_control(): 启动循环模式run_once(): 执行单次模式
- 每个服务器实例在独立线程中运行
- 线程命名格式:
Thread-{server_ip} - 主线程等待所有子线程完成 (
thread.join())
- 使用 YAML 格式配置:
fan_settings.yaml(从fan_settings.yaml.template复制) - 支持多服务器配置,每个服务器可定义多个温度区间和对应风扇转速
- IP 地址可设置为
"local"以在本地直接执行命令(无需远程 IPMI) - 邮件告警 (可选,默认关闭): 风扇调节连续失败时自动发送告警邮件
- 可配置转速阈值和失败次数
- 支持多个收件人
- 防止邮件轰炸(最小间隔 1 小时)
- 使用
TimedRotatingFileHandler按天轮转日志 - 日志保留天数由配置文件中的
log_backup_count控制 - 同时输出到文件 (
logs/fancontroller.log) 和控制台 - 日志格式:
时间戳 - 线程名 - 消息内容
- 使用
max(cpu_temps)作为判断依据(最热 CPU 温度) - 温度范围匹配:
min_temp <= avg_temp < max_temp - 优化策略: 温度未跨越区间且风扇未异常飙升时跳过设置
- 风扇异常检测:
max_speed >= 15000(接近max_fan_rotational_speed = 16000)
pip install -r requirements.txt# 复制模板文件
cp fan_settings.yaml.template fan_settings.yaml
# 编辑配置文件,设置服务器 IP、用户名、密码和温度区间
# 注意: 只能使用 IP 地址,不能使用域名循环控制模式(程序内部循环)
# 前台运行
python fancontroller.py
# 后台运行(Windows)
start /b python fancontroller.py
# 后台运行(Linux)
nohup python3 fancontroller.py &单次执行模式(外部调度)
# 直接执行一次
python fancontroller_once.py
# cron 定时执行(每 10 分钟)
*/10 * * * * /usr/bin/python3 /path/to/fancontroller_once.py
# Windows 任务计划程序
schtasks /create /tn "IPMI Fan Controller" /tr "python C:\path\to\fancontroller_once.py" /sc minute /mo 10作为 systemd 服务运行 (Linux 推荐)
# 创建服务文件
sudo nano /etc/systemd/system/fancontroller.service
# 重载配置并启动服务
sudo systemctl daemon-reload
sudo systemctl start fancontroller.service
sudo systemctl status fancontroller.service
sudo systemctl enable fancontroller.service
# 查看服务日志
journalctl -u fancontroller.service -f# 实时查看日志
tail -f logs/fancontroller.log
# 查看历史日志
ls logs/
cat logs/fancontroller.log.YYYY-MM-DD架构原则(2026-09-28 改造后):「读」统一,「写」分机型。 风扇转速与温度都由基类从 Prometheus 取(数据源是 ipmi_exporter / DCGM), 子类只负责实现该机型特有的「写」命令。
- 在
fanController/目录下创建新的控制器类文件(如epycd8_controller.py) - 继承
IPMIFanController基类,实现该机型特有的写操作:set_fan_speed(fan_index, percentage): 设置风扇转速的 IPMI raw 命令_get_base_command(): 生成基础命令前缀(ip: "local"时返回空串)set_ipmi_manual_mode()/set_ipmi_auto_mode(): 手动/自动模式切换 (部分机型如 EPYCD8 没有独立的「切手动」命令,写占空比本身就是手动)_build_temperature_query()(可选): 换温度源时覆盖它。 默认查 CPU 核心温度(node_exporter 的 hwmon,按语义标签Tctl过滤); EPYCD8 覆盖此方法去查 GPU 温度(DCGM),因为它的机箱风扇是给 GPU 散热的start_fan_control()/run_once(): 入口方法
- 不需要实现
get_fan_rotational_speed()或get_cpu_temperature()—— 两者都已由基类统一从 Prometheus 获取(原先各机型各写一份 sdr 解析, 列结构还互不相同) - 在
fancontroller.py与fancontroller_once.py的CONTROLLER_TYPES字典里登记一行:'新机型名': 新控制器类
- Windows 平台使用配置文件中的
windows_ipmi_tool_path指定的 ipmitool.exe - Linux 平台直接使用系统的
ipmitool命令(需预先安装) - 本地模式 (
ip: "local"): 命令格式无需-I lanplus -H ...前缀
初始化命令 (必须)
- 禁用"第三方 PCIe 卡的散热响应策略":
raw 0x30 0xce 0x00 0x16 0x05 0x00 0x00 0x00 0x05 0x00 0x01 0x00 0x00 - 成功返回:
16 05 00 00 00 - 此命令需要在设置手动模式前执行,否则风扇控制可能不生效
IPMI 命令
- CPU 温度传感器识别:
"0Eh"或"0Fh"标识符 - 手动模式 IPMI 命令:
raw 0x30 0x30 0x01 0x00 - 自动模式 IPMI 命令:
raw 0x30 0x30 0x01 0x01 - 设置风扇转速:
raw 0x30 0x30 0x02 0x{fan_index:02x} 0x{percentage:02x}
「读」全部统一走 Prometheus,不再由各机型子类执行 ipmitool sdr:
prometheus:
base_url: "http://192.0.2.20:30091"
timeout: 10
servers:
- type: epycd8
prometheus: # per-server 覆盖(多机场景必需)
fan_instance: "192.0.2.10:9290" # 风扇转速 ← ipmi_exporter
temp_instance: "192.0.2.10:9100" # CPU 温度 ← node_exporter
gpu_instance: "192.0.2.10:9400" # GPU 温度 ← DCGM exporter| 数据 | 指标 | instance(实测示例) |
|---|---|---|
| 风扇转速 | ipmi_fan_speed_rpm{name="FRNT_FAN1"} |
192.0.2.10:9290 |
| CPU 温度 | node_hwmon_temp_celsius(按语义标签 label="Tctl" 过滤) |
192.0.2.10:9100 |
| GPU 温度 | DCGM_FI_DEV_GPU_TEMP |
192.0.2.10:9400 |
fan_instance / temp_instance / gpu_instance(笼统的 instance 仍
作兜底)。
scrape_interval 实测为 30s,也就是温度最多滞后 30 秒 —— 对控速决策是明显
滞后,建议把这些 target 的抓取间隔调小(node_exporter 采集很轻,10s 毫无压力)。
补充:CPU 温度走
node_exporter --collector.hwmon,它读的就是内核 hwmon (/sys/class/hwmon/),与直接读 sysfs 是同一份数据。注意 hwmon 里 k10temp 的 chip 名是 PCI 路径形式(pci0000:00_0000:00:18_3)而非可读的k10temp, 所以查询用node_hwmon_sensor_label{label="Tctl"}做语义过滤,别硬编码 chip 名。
- PyYAML: 用于解析 YAML 配置文件
- 其他功能仅依赖 Python 标准库(Prometheus 查询用
urllib实现,无需requests)
| 品牌 | 型号 | Type 配置值 |
|---|---|---|
| Dell | 730XD | dell730 |
| Dell | 730 | dell730 |
| ASRock Rack | EPYCD8 | epycd8 |
上面的内容是上游遗留的 fancontroller.py 那套(Dell 730 为主,CPU 温度控速)。
本仓库真正在用的是 app/(FastAPI 控制台)+ frontend/(Vue3 前端):
- 控速依据是 GPU 温度(DCGM),目标机的机箱风扇,写
ipmitool raw 0x3a 0x01 - 运行时状态(模式 / 管控 GPU / 分配关系 / 审计)全部落 SQLite,配置文件只是首次运行的种子
| 模块 | 职责 |
|---|---|
main.py |
入口:单进程 = 控制回路 + API + 静态托管;STATIC_DIR 来自 runtime.py |
api.py |
REST + WebSocket(/api/status、/api/curve/preview、/ws 等) |
controller.py |
控制回路(默认 15s 一轮,可配) |
curve.py |
分段曲线 + 滞回(升温立即生效,降温须跌出滞回带) |
ipmi.py |
raw 0x3a 0x01 命令族与占空比编码 |
sensors.py |
DCGM / ipmi_exporter / node_exporter / nvidia-smi 兜底 / Prometheus 历史 |
safety.py |
安全护栏(退出回退 BMC 自动档 + atexit + 心跳) |
store.py |
SQLite(data/fan-console.db,唯一权威数据源) |
config.py |
pydantic 配置模型;load_config 缺文件时优雅降级为默认值 |
runtime.py |
资源定位中枢:源码运行根 = app/ 包目录;PyInstaller 冻结运行根 = exe 所在目录。config / DB / static / heartbeat 四处路径全部从这里取——改路径相关代码时只动这里,别在各自模块里再写 __file__ 推导 |
CI(.github/workflows/ci.yml,推 main / PR / 打 v* tag 触发):
test-and-build:后端单测(Python 3.13)→ 前端npm ci && npm run build(含 vue-tsc 全量类型检查)→ 打部署包gpu-fan-console-app.tgz(成员路径app/...,排除app/data)挂 Artifactsbuild-console/build-legacy(非 PR 才跑):PyInstaller 双平台可执行;分发压缩包由packaging/build_console.py内置生成(zipfile/tarfile,不要在 workflow 里用 tar/PowerShell 打——Windows runner 的 cp1252 控制台编不了中文输出,打包脚本必须sys.stdout.reconfigure(encoding="utf-8"))release(打 tag 才跑):gh release create自动发版附全部产物;必须显式--repo "$GITHUB_REPOSITORY"(该 job 无 checkout,gh 推断不出仓库)
冻结(PyInstaller)相关:
- 打包入口
entry_console.py(控制台 onedir)、packaging/build_console.py/packaging/build_legacy.py(旧脚本 onefile) - 冻结后可变资源必须落 exe 旁(
_MEIPASS每次启动清空):config.yaml /data// heartbeat 由runtime.py定位到 exe 旁;旧脚本fancontroller(.once).py的_app_directory()同理 - 本机已实测:控制台 exe(API/静态页/SQLite 落位)、旧脚本 exe(缺配置报错、空配置正常退出)
- 部署方式、踩坑(tar 路径对齐 / upload 分片 base64 / 远端 rm 黑名单)、回滚、验证清单 →
DEPLOY.md - 一键脚本
./deploy.sh:CONN=<ssh连接名>必填,可选REMOTE_DIR/SERVICE/URL; 脚本打包时排除app/data与app/config.yaml - 服务器直连 GitHub 可用时,更快的更新方式:直接
curl -LRelease 页的gpu-fan-console-app.tgz,免去本地构建与分片上传(v2.0 实测可行) ⚠️ 升级已配置的机器时,解压必须--exclude='app/config.yaml'——仓库里的 config.yaml 是模板(示例地址),覆盖会毁掉现场配置
- 公开仓库,严禁出现真实内网 IP / 主机名 / 凭据。示例地址一律用 RFC 5737 文档段(192.0.2.0/24 等);写文档、注释、测试样例时同样遵守
config.yaml按模板维护;README_EN.md与中文版结构对齐- 实时数据源:三个外部 exporter(自备组件,装法不限):
| 数据 | exporter | 项目地址 | 指标 | 默认端口 |
|---|---|---|---|---|
| GPU 温度/功率/频率 | dcgm-exporter | https://github.com/NVIDIA/dcgm-exporter | DCGM_FI_DEV_GPU_TEMP 等 |
9400 |
| 风扇转速 | ipmi_exporter | https://github.com/prometheus-community/ipmi_exporter | ipmi_fan_speed_rpm |
9290 |
| CPU 温度 | node_exporter | https://github.com/prometheus/prometheus/tree/master/node_exporter | node_hwmon_temp_celsius(label="Tctl";启动必须带 --collector.hwmon) |
9100 |
端点在 app/config.yaml 的 sources.*_endpoint(实时读数)与 prometheus_*_instance
(仅 /api/history 用)可配;DCGM 不可用时 sensors.py 降级 nvidia-smi 兜底。
ipmi_exporter in-band 读 /dev/ipmi0 需要 root。安装/自检详见 DEPLOY.md 2.1 节。