7 | 8 | - 2023 XX XX | >Since last year my R720's motherboard has been dying. Not sure if I'll get around to do finish the full 2.0 rework of this script since I won't really use it, but who knows, could gamble of me getting incredibly bored at some point. 9 | - 2022 07 11 | >minor update: Swapping CPU IDs fixing single CPU detection failure. 10 | - 2022 05 13 | >minor update: Adding custom failsafe value. Work In Progress branch for upcoming R5. 11 | - 2022 03 04 | >R4 patch1 : Adding new CPU Data source option, minor log corrections. 12 | - 2022 03 03 | >R4 Deltacheck CPU mode, DeltaA/E with Ambient check, failsafes, infinite CPU count. 13 | - 2022 02 27 | >minor update: adding IPMI-fail fail-safe. 14 | - 2022 02 27 | >R3 Auto CPUn/Ambient mode switching, logging, auto hexadecimal conversion, and more. 15 | - 2022 02 01 | >minor update: beginner friendly guide for beginner friendly Unraid and minor edits. 16 | - 2021 11 10 | >/!\ r2f : fixing a small, but quite critical fluke. 17 | - 2021 08 19 | >R2 update: now takes in account intake and exhaust temperatures! 18 | - 2021 04-08 | > various shit edits 19 | - 2021 04 14 | >R1 initial dump from my running environment & comments 20 |
21 |
50 |
51 | ```bash
52 | #!/bin/bash
53 | #the IP address of iDrac
54 | IPMIHOST=192.168.0.42
55 |
56 | #iDrac user
57 | IPMIUSER=root
58 |
59 | #iDrac password (calvin is the default password)
60 | IPMIPW=calvin
61 |
62 | #YOUR IPMI ENCRYPTION KEY
63 | IPMIEK=0000000000000000000000000000000000000000
64 |
65 | #Side note: you shouldn't ever store credentials in a script. Period. Here it's an example.
66 | #I suggest you give a look at tools like https://github.com/plyint/encpass.sh
67 |
68 | #Failsafe mode
69 | #(Possible values being a number between 80 and 100, or "auto")
70 | E_value="auto"
71 |
72 | #IPMI IDs
73 | CPUID0=0Eh
74 | CPUID1=0Fh
75 | CPUID2="0#h"
76 | CPUID3="0#h"
77 | AMBIENT_ID=04h
78 | EXHAUST_ID=01h
79 |
80 | #Non-IPMI data source for CPU:
81 | NICPU_toggle=false
82 | NICPUdatadump_command=(sensors -A)
83 | NICPUdatadump_device="coretemp-isa-"
84 | NICPUdatadump_device_num=4
85 | NICPUdatadump_core=Core
86 | NICPUdatadump_cut="-c16-18"
87 | NICPUdatadump_offset=0
88 | IPMIDATA_toggle=true
89 |
90 | #Logtype:
91 | #0 = Only Alerts
92 | #1 = Fan speed output + alerts
93 | #2 = Simple text + fanspeed output + alerts
94 | #3 = Table + fanspeed output + alerts
95 | Logtype=2
96 |
97 | #There you basically define your fan curve.
98 | TEMP_STEP0=30
99 | FST0=2
100 | TEMP_STEP1=35
101 | FST1=6
102 | TEMP_STEP2=40
103 | FST2=8
104 | TEMP_STEP3=50
105 | FST3=10
106 | TEMP_STEP4=60
107 | FST4=12
108 | TEMP_STEP5=75
109 | FST5=20
110 | #CPU fan governor type
111 | TEMPgov=0
112 | CPUdelta=15
113 |
114 | #These values are used as steps for the intake temps.
115 |
116 | AMBTEMP_STEP0=20
117 | AMBTEMP_MOD_STEP0=0
118 | AMBTEMP_noCPU_FS_STEP0=8
119 |
120 | AMBTEMP_STEP1=21
121 | AMBTEMP_MOD_STEP1=10
122 | AMBTEMP_noCPU_FS_STEP1=15
123 |
124 | AMBTEMP_STEP2=24
125 | AMBTEMP_MOD_STEP2=15
126 | AMBTEMP_noCPU_FS_STEP2=20
127 |
128 | AMBTEMP_STEP3=26
129 | AMBTEMP_MOD_STEP3=20
130 | AMBTEMP_noCPU_FS_STEP3=30
131 |
132 | MAX_MOD=69
133 |
134 | EXHTEMP_MAX=65
135 |
136 | #Ambient fan mode - Delta mode
137 | AMBDeltaMode=true
138 | DeltaR=3
139 |
140 | #Log loop debug - true or false, logging of loops for debugging script
141 | Logloop=false
142 |
143 | #Looplog prefix
144 | l="Loop -"
145 |
146 | #Hexadecimal conversion and IPMI command into a function
147 | ipmifanctl=(ipmitool -I lanplus -H "$IPMIHOST" -U "$IPMIUSER" -P "$IPMIPW" -y "$IPMIEK" raw 0x30 0x30)
148 | function setfanspeed () {
149 | TEMP_Check=$1
150 | TEMP_STEP=$2
151 | FS=$3
152 | if [[ $FS == "auto" ]]; then
153 | if [ "$Logtype" != 0 ] && [ "$4" -eq 0 ]; then
154 | echo "> $TEMP_Check °C is higher or equal to $TEMP_STEP °C. Switching to automatic fan control"
155 | fi
156 | [ "$4" -eq 1 ] && echo "> ERROR : Keeping fans on auto as safety measure"
157 | "${ipmifanctl[@]}" 0x01 0x01
158 | exit $4
159 | else
160 | if [[ $FS -gt "100" ]]; then
161 | FS=100
162 | fi
163 | HEX_value=$(printf '%#04x' "$FS")
164 | if [ "$4" -eq 1 ]; then
165 | echo "> ERROR : Keeping fans on high profile ($3 %) as safety measure"
166 | elif [ "$Logtype" != 0 ]; then
167 | echo "> $TEMP_Check °C is lower or equal to $TEMP_STEP °C. Switching to manual $FS % control"
168 | fi
169 | "${ipmifanctl[@]}" 0x01 0x00
170 | "${ipmifanctl[@]}" 0x02 0xff "$HEX_value"
171 | exit $4
172 | fi
173 | }
174 | #Failsafe = Parameter check
175 | re='^[0-9]+$'
176 | ren='^[+-]?[0-9]+?$'
177 | if [ "$Logloop" != false ] && [ "$Logloop" != true ]; then
178 | echo "Logloop parameter invalid, must be true or false!"
179 | setfanspeed XX XX "$E_value" 1
180 | fi
181 | if [ "$AMBDeltaMode" != false ] && [ "$AMBDeltaMode" != true ]; then
182 | echo "AMBDeltaMode parameter invalid, must be true or false!"
183 | setfanspeed XX XX "$E_value" 1
184 | fi
185 | if [[ "$DeltaR" =~ $ren ]]; then
186 | if [ "$DeltaR" -le "0" ]; then
187 | echo "DeltaR parameter invalid, must be greater than 0!"
188 | setfanspeed XX XX "$E_value" 1
189 | fi
190 | else
191 | echo "DeltaR parameter invalid, not a number!"
192 | setfanspeed XX XX "$E_value" 1
193 | fi
194 | if [[ "$CPUdelta" =~ $ren ]]; then
195 | if [ "$CPUdelta" -le "0" ]; then
196 | echo "CPUdelta parameter invalid, must be greater than 0!"
197 | setfanspeed XX XX "$E_value" 1
198 | fi
199 | else
200 | echo "CPUdelta parameter invalid, not a number!"
201 | setfanspeed XX XX "$E_value" 1
202 | fi
203 | if [ "$TEMPgov" != 1 ] && [ "$TEMPgov" != 0 ]; then
204 | echo "TEMPgov parameter invalid, can only be 0 or 1!"
205 | setfanspeed XX XX "$E_value" 1
206 | fi
207 | if [[ "$Logtype" =~ $ren ]]; then
208 | if [ "$Logtype" -lt 0 ] || [ "$Logtype" -gt 3 ]; then
209 | echo "Logtype parameter invalid, must be in 0-3 range!"
210 | setfanspeed XX XX "$E_value" 1
211 | fi
212 | else
213 | echo "Logtype parameter invalid, not a number!"
214 | setfanspeed XX XX "$E_value" 1
215 | fi
216 | if [[ "$EXHTEMP_MAX" =~ $ren ]]; then
217 | if [ "$EXHTEMP_MAX" -lt 0 ]; then
218 | echo "EXHTEMP_MAX parameter invalid, can't be negative!"
219 | setfanspeed XX XX "$E_value" 1
220 | fi
221 | else
222 | echo "EXHTEMP_MAX parameter invalid, not a number!"
223 | setfanspeed XX XX "$E_value" 1
224 | fi
225 | if [[ $MAX_MOD =~ $ren ]]; then
226 | if [ "$MAX_MOD" -lt 0 ]; then
227 | echo "MAX_MOD parameter invalid, can't be negative!"
228 | setfanspeed XX XX "$E_value" 1
229 | fi
230 | else
231 | echo "MAX_MOD parameter invalid, not a number!"
232 | setfanspeed XX XX "$E_value" 1
233 | fi
234 | if [[ "$E_value" =~ $ren ]]; then
235 | if [ "$E_value" -lt 80 ]; then
236 | echo "E_value parameter invalid, can't be negative or lower than 80"
237 | E_value="auto"
238 | fi
239 | if [ "$E_value" -gt 100 ]; then
240 | echo "E_value parameter invalid, can't be greater than 100"
241 | E_value="auto"
242 | fi
243 | elif [ "$E_value" != "auto" ]; then
244 | echo "E_value parameter invalid, not a number!"
245 | E_value="auto"
246 | fi
247 | #Counting CPU Fan speed steps and setting max value
248 | if $Logloop ; then
249 | echo "$l New loop => Counting CPU Fan speed steps and setting max value"
250 | fi
251 | for ((i=0; i>=0 ; i++))
252 | do
253 | inloopstep="TEMP_STEP$i"
254 | inloopspeed="FST$i"
255 | if [[ ! -z "${!inloopspeed}" ]] && [[ ! -z "${!inloopstep}" ]]; then
256 | if $Logloop ; then
257 | echo "$l CPU Temperature step n°$i = ${!inloopstep}°C"
258 | echo "$l Fan speed step n°$i = ${!inloopspeed}%"
259 | fi
260 | if ! [[ "${!inloopstep}" =~ $ren ]]; then
261 | echo "Butterfinger failsafe: CPU Temperature step n°$i isn't a number!"
262 | setfanspeed XX XX "$E_value" 1
263 | fi
264 | if [[ "${!inloopspeed}" =~ $ren ]]; then
265 | if [[ "${!inloopspeed}" -lt 0 ]]; then
266 | echo "Butterfinger failsafe: Fan speed step n°$i is negative!"
267 | setfanspeed XX XX "$E_value" 1
268 | fi
269 |
270 | else
271 | echo "Butterfinger failsafe: Fan speed step n°$i isn't a number!"
272 | setfanspeed XX XX "$E_value" 1
273 | fi
274 | else
275 | inloopmaxstep="TEMP_STEP$((i-1))"
276 | if [ $((i-1)) -le 0 ]; then
277 | echo "Butterfinger failsafe: no CPU stepping found!!"
278 | setfanspeed XX XX "$E_value" 1
279 | fi
280 | MAXTEMP="${!inloopmaxstep}"
281 | TEMP_STEP_COUNT=$i
282 | if $Logloop ; then
283 | echo "$l CPU temperature step count = $i"
284 | echo "$l CPU max temperature to auto mode = $MAXTEMP°C"
285 | echo "$l CPU Temp Steps counting = stop"
286 | fi
287 | break
288 | fi
289 | done
290 | #Counting Ambiant Fan speed and MOD steps and setting max value
291 | if $Logloop ; then
292 | echo "$l New loop => Counting Ambiant Fan speed and MOD steps and setting max value"
293 | fi
294 | for ((i=0; i>=0 ; i++))
295 | do
296 | inloopstep="AMBTEMP_STEP$i"
297 | inloopspeed="AMBTEMP_noCPU_FS_STEP$i"
298 | inloopmod="AMBTEMP_MOD_STEP$i"
299 | if [[ ! -z "${!inloopspeed}" ]] && [[ ! -z "${!inloopmod}" ]] && [[ ! -z "${!inloopstep}" ]]; then
300 | if $Logloop ; then
301 | echo "$l Ambient temperature step n°$i = ${!inloopstep}°C"
302 | echo "$l Ambient modifier for CPU temp step n°$i = ${!inloopmod}°C"
303 | echo "$l Ambient NO CPU fan speed step n°$i = ${!inloopspeed}%"
304 | fi
305 | if ! [[ "${!inloopstep}" =~ $ren ]]; then
306 | echo "Butterfinger failsafe: Ambient temperature step n°$i isn't a number!"
307 | setfanspeed XX XX "$E_value" 1
308 | fi
309 | if [[ "${!inloopmod}" =~ $ren ]]; then
310 | if [[ "${!inloopmod}" -lt 0 ]]; then
311 | echo "Beware: Ambient modifier for CPU temp step n°$i is negative!"
312 | echo "Proceeding..."
313 | fi
314 |
315 | else
316 | echo "Butterfinger failsafe: Ambient modifier for CPU temp step n°$i isn't a number!"
317 | setfanspeed XX XX "$E_value" 1
318 | fi
319 | if [[ "${!inloopspeed}" =~ $ren ]]; then
320 | if [[ "${!inloopspeed}" -lt 0 ]]; then
321 | echo "Butterfinger failsafe: Ambient NO CPU fan speed step n°$i is negative!"
322 | setfanspeed XX XX "$E_value" 1
323 | fi
324 |
325 | else
326 | echo "Butterfinger failsafe: Ambient NO CPU fan speed step n°$i isn't a number!"
327 | setfanspeed XX XX "$E_value" 1
328 | fi
329 | else
330 | inloopmaxstep="AMBTEMP_STEP$((i-1))"
331 | if [ $((i-1)) -le 0 ]; then
332 | echo "Butterfinger failsafe: no Ambient stepping found!!"
333 | setfanspeed XX XX "$E_value" 1
334 | fi
335 | AMBTEMP_MAX="${!inloopmaxstep}"
336 | AMB_STEP_COUNT=$i
337 | if $Logloop ; then
338 | echo "$l Ambient temperature step count = $i"
339 | echo "$l Ambient max temperature to max mod = $AMBTEMP_MAX°C"
340 | echo "$l CPU Ambiant Steps counting = stop"
341 | fi
342 | break
343 | fi
344 | done
345 | #Pulling temperature data from IPMI
346 | if $IPMIDATA_toggle ; then
347 | IPMIPULLDATA=$(ipmitool -I lanplus -H $IPMIHOST -U $IPMIUSER -P $IPMIPW -y $IPMIEK sdr type temperature)
348 | DATADUMP=$(echo "$IPMIPULLDATA")
349 | if [ -z "$DATADUMP" ]; then
350 | echo "No data was pulled from IPMI"
351 | setfanspeed XX XX "$E_value" 1
352 | else
353 | AUTOEM=false
354 | fi
355 | else
356 | if $NICPU_toggle ; then
357 | AUTOEM=false
358 | else
359 | echo "Both IPMI data and Non-IPMI-CPU data are toggled off"
360 | setfanspeed XX XX "$E_value" 1
361 | fi
362 | fi
363 | #Parsing CPU Temp data into values to be later checked in count, continuity and value validity.
364 | if $NICPU_toggle ; then
365 | echo "Non-IPMI data source. An error can be thrown without incidence."
366 | if $Logloop ; then
367 | echo "$l New loop => Pulling data dynamically from Non-IPMI source"
368 | fi
369 | for ((j=0; j>=0 ; j++))
370 | do
371 | [ -z "$socketcount" ] && socketcount=0
372 | datadump=$("$NICPUdatadump_command" "$NICPUdatadump_device$(printf "%0"$NICPUdatadump_device_num"d" "$socketcount")")
373 | if [[ ! -z $datadump ]]; then
374 | if $Logloop ; then
375 | echo "$l Detected CPU socket $socketcount !!"
376 | echo "$l New loop => Parsing CPU Core data"
377 | fi
378 | socketcount=$((socketcount+1))
379 | for ((i=0; i>=0 ; i++))
380 | do
381 | [ -z "$corecount" ] && corecount=0
382 | Corecountloop_data=$( echo "$datadump" | grep -A 0 "$NICPUdatadump_core $i"| cut "$NICPUdatadump_cut")
383 | if [[ ! -z $Corecountloop_data ]]; then
384 | declare CPUTEMP$corecount="$((Corecountloop_data+NICPUdatadump_offset))"
385 | if $Logloop ; then
386 | echo "$l Defining CPUTEMP$corecount with value : $((CPUTEMP$corecount))"
387 | fi
388 | corecount=$((corecount+1))
389 | else
390 | if $Logloop ; then
391 | echo "$l CPU Core data parsing on CPU Socket $((socketcount-1)) = stop"
392 | fi
393 | break
394 | fi
395 | done
396 | else
397 | echo "Non-IPMI detection : done."
398 | if $Logloop ; then
399 | echo "$l Result : $corecount Total CPU temperature sources added."
400 | echo "$l CPU Data parsing from Non-IPMI source = stop"
401 | fi
402 | break
403 | fi
404 | done
405 | else
406 | CPUTEMP0=$(echo "$DATADUMP" |grep "$CPUID0" |grep degrees |grep -Po '\d{2}' | tail -1)
407 | CPUTEMP1=$(echo "$DATADUMP" |grep "$CPUID1" |grep degrees |grep -Po '\d{2}' | tail -1)
408 | CPUTEMP2=$(echo "$DATADUMP" |grep "$CPUID2" |grep degrees |grep -Po '\d{2}' | tail -1)
409 | CPUTEMP3=$(echo "$DATADUMP" |grep "$CPUID3" |grep degrees |grep -Po '\d{2}' | tail -1)
410 | fi
411 | #CPU counting
412 | if [ -z "$CPUTEMP0" ]; then
413 | CPUcount=0
414 | else
415 | if [[ ! -z "$CPUTEMP0" ]]; then #Infinite CPU number adding, if you pull individual CPU cores from lm-sensors or something
416 | for ((i=0; i>=0 ; i++))
417 | do
418 | CPUcountloop="CPUTEMP$i"
419 | if [[ ! -z "${!CPUcountloop}" ]]; then
420 | if $Logloop ; then
421 | echo "$l CPU detection = CPU$i detected / Value = ${!CPUcountloop}"
422 | fi
423 | if ! [[ "${!CPUcountloop}" =~ $re ]] ; then
424 | echo "!!error: Reading is not a number or negative!!"
425 | echo "Falling back to ambient mode..."
426 | CPUcount=0
427 | break
428 | fi
429 | currcputemp="${!CPUcountloop}"
430 | CPUcount=$((i+1))
431 | TEMPadd=$((TEMPadd+currcputemp))
432 | else
433 | if [[ $((CPUcount % 2)) -eq 0 ]] || [[ $CPUcount -eq 1 ]]; then
434 | if $Logloop ; then
435 | if [ "$CPUcount" -eq "1" ]; then
436 | echo "$l CPU count : $CPUcount CPU detected!"
437 | else
438 | echo "$l CPU count is even : $CPUcount CPU detected!"
439 | fi
440 | echo "$l CPU counting = stop"
441 | fi
442 | CPUn=$((TEMPadd/CPUcount))
443 | break
444 | else
445 | CPUcount=0
446 | echo "CPU count is odd, please check your configuration";
447 | echo "Falling back to ambient mode..."
448 | break
449 | fi
450 | fi
451 | done
452 |
453 | fi
454 | fi
455 | #CPU Find lowest and highest CPU temps
456 | if [ "$CPUcount" -gt 1 ]; then
457 | if $Logloop ; then
458 | echo "$l New loop => Finding highest and lowest CPU temps"
459 | fi
460 | for ((i=0; i
875 |
876 | Instead of boring you with text, here's the alphabet of them:
877 |
878 | Field Name | Mandatory | Allowed Values | Allowed Special Characters |
879 | ------ | ------- | ------- | ------- |
880 | Minutes | YES | 0 - 59 | , - \* / |
881 | Hours | YES | 0 - 23 | , - \* / |
882 | Day of month | YES | 1 - 31 | , - \* ? / L W |
883 | Month | YES | 1 - 12 (representing Jan - Dec), JAN - DEC (case-insensitive), JANUARY - DECEMBER (case-insensitive) | , - \* / |
884 | Day of week | YES | 0 - 6, 7 (representing Sun - Sat and Sun again), SUN - SAT (case-insensitive), SUNDAY - SATURDAY (case-insensitive) | , - \* ? / L # |
885 | Year | NO | empty or 1970-2099 | , - \* / |
886 |
887 | And here a cheatsheet, you'll probably find what you're looking for in it, or be able to make it from it.
888 |
889 | Cron Expression examples | Meaning |
890 | --------- | --------- |
891 | \* \* \* \* \* 2022 | Execute a cron job every minute during the year 2022 |
892 | \* \* \* \* \* | Execute a cron job every minute |
893 | \*/5 \* \* \* \* | Execute a cron job every 5 minutes |
894 | 0 \* \* \* \* | Execute a cron job every hour |
895 | 0 12 \* \* \* | Fire at 12:00 PM (noon) every day |
896 | 15 10 \* \* \* | Fire at 10:15 AM every day |
897 | 15 10 \* \* ? | Fire at 10:15 AM every day |
898 | 15 10 \* \* \* 2022-2024 | Fire at 10:15 AM every day during the years 2022, 2023 and 2024 |
899 | \* 14 \* \* \* | Fire every minute starting at 2:00 PM and ending at 2:59 PM, every day |
900 | 0/5 14,18 \* \* \* | Fire every 5 minutes starting at 2:00 PM and ending at 2:55 PM, AND fire every 5 minutes starting at 6:00 PM and ending at 6:55 PM, every day |
901 | 0-5 14 \* \* \* | Fire every minute starting at 2:00 PM and ending at 2:05 PM, every day |
902 | 10,44 14 \* 3 3 | Fire at 2:10 PM and at 2:44 PM every Wednesday in the month of March. |
903 | 15 10 \* \* 1-5 | Fire at 10:15 AM every Monday, Tuesday, Wednesday, Thursday and Friday |
904 | 15 10 15 \* \* | Fire at 10:15 AM on the 15th day of every month |
905 | 15 10 L \* \* | Fire at 10:15 AM on the last day of every month |
906 | 15 10 \* \* 5L | Fire at 10:15 AM on the last Friday of every month |
907 | 15 10 \* \* 5#3 | Fire at 10:15 AM on the third Friday of every month |
908 | 0 12 1/5 \* \* | Fire at 12:00 PM (noon) every 5 days every month, starting on the first day of the month. |
909 | 11 11 11 11 \* | Fire every November 11th at 11:11 AM. |
910 | 11 11 11 11 \* 2022 | Fire at 11:11 AM on November 11th in the year 2022. |
911 | 0 0 \* \* 3 | Fire at midnight of each Wednesday. |
912 | 0 0 1,2 \* \* | Fire at midnight of 1st, 2nd day of each month |
913 | 0 0 1,2 \* 3 | Fire at midnight of 1st, 2nd day of each month, and each Wednesday. |
914 |
915 |
872 | Everything you need should be here
873 |
874 |