2023-10-03 12:48:52 +02:00
# hdparm - get/set SATA/IDE device parameters
# -q Handle the next option quietly, suppressing normal output (but not error messages).
# -I Print device information.
# -C Check the current IDE power mode status:
# unknown (drive does not support this command)
# active/idle (normal operation)
# standby (low power mode, drive has spun down)
# sleeping (lowest power mode, drive is completely shut down)
# -S Set the standby (spindown) timeout for the drive.
# The timeout specifies how long to wait in idle (with no disk activity) before turning off the motor to save power.
# The value of 0 disables spindown,
# the values from 1 to 240 specify multiples of 5 seconds
# and values from 241 to 251 specify multiples of 30 minutes.
# -y Force an IDE drive to immediately enter the low power consumption standby mode, usually causing it to spin down.
# Get power status:
# sudo hdparm -C /dev/disk/by-id/ata-WDC_WD40EFRX-68N32N0_WD-WCC7K0CPF0N1
# #=> drive state is: active/idle
#
2023-10-04 14:23:01 +02:00
# Spin down after 2 minutes (120s/5s = 24).
# TODO: Western Digital (WD) Red/Green drives don't work properly. For low `-S` values, they spin down after **10** minutes. And for high values they don't spin down at all.
# - https://superuser.com/questions/1746074/spin-down-not-working-on-wd-red
# - https://askubuntu.com/questions/196473/setting-sata-hdd-spindown-time-for-western-digital-green-drives
#
2023-10-03 12:48:52 +02:00
# sudo hdparm -S 24 /dev/disk/by-id/ata-WDC_WD40EFRX-68N32N0_WD-WCC7K0CPF0N1 # 4tb1
# sudo hdparm -S 24 /dev/disk/by-id/ata-WDC_WD30EFRX-68EUZN0_WD-WCC4N1173157 # 3tb1
# sudo hdparm -S 24 /dev/disk/by-id/ata-WDC_WD30EFRX-68EUZN0_WD-WMC4N0564095 # 3tb2
#
2023-10-04 14:23:01 +02:00
# ... after > 10 minutes ...
2023-10-03 12:48:52 +02:00
#
# Query disk without waking it up:
# https://wiki.archlinux.org/title/hdparm#Querying_the_status_of_the_disk_without_waking_it_up
# sudo smartctl -i -n standby /dev/disk/by-id/ata-WDC_WD40EFRX-68N32N0_WD-WCC7K0CPF0N1
# #=> Device is in STANDBY mode, exit(2)
#
# Get power status:
# sudo hdparm -C /dev/disk/by-id/ata-WDC_WD40EFRX-68N32N0_WD-WCC7K0CPF0N1
# sudo hdparm -C /dev/disk/by-id/ata-WDC_WD30EFRX-68EUZN0_WD-WCC4N1173157
# sudo hdparm -C /dev/disk/by-id/ata-WDC_WD30EFRX-68EUZN0_WD-WMC4N0564095
# #=> drive state is: standby
# Power savings.
# With 12tb1, 3tb1, 3tb2 spinning and 4tb1 spun-down: 26.1 W
# With 12tb1 spinning and 3tb1, 3tb2, 4tb1 spun-down: 19.8 W
# -> WD30EFRX measured idle spinning vs standby = +3.15 W
# -> WD30EFRX advertised idle spinning vs unplugged = +3.0 W
# See also: Systemd service to spin down after boot. https://wiki.archlinux.org/title/hdparm#Putting_a_drive_to_sleep_directly_after_boot
# TODO: For external (USB-)disks, use `hd-idle`.
# https://www.reddit.com/r/NixOS/comments/751i5t/comment/do3f3l7/
# # Options:
# # -a <disk>: Select disk for subsequent parameters.
2023-10-04 14:23:01 +02:00
# # -i 120: Spin down after 2 minutes (120 seconds) of inactivity.
# ExecStart = "${pkgs.hd-idle}/bin/hd-idle -a /dev/disk/by-id/XXX-XXX-XXX -i 120";
2023-10-03 12:48:52 +02:00
2023-11-23 12:08:27 +01:00
{ lib , config , options , pkgs , modulesPath , . . . }:
with lib ;
let
cfg = config . yoda . spin-down-hdd ;
in
2023-10-03 12:48:52 +02:00
{
2023-11-23 12:08:27 +01:00
options = {
yoda . spin-down-hdd = mkOption {
type = types . listOf types . str ;
default = [ ] ;
example = [ " a t a - W D C _ W D 4 0 E F R X - 6 8 N 3 2 N 0 _ W D - W C C 7 K 0 C P F 0 N 1 " ] ;
description = ''
List with IDs ( /dev/disk/by-id / <ID> ) of HDDs to spin down after 10 minutes .
'' ;
} ;
2023-10-03 12:48:52 +02:00
} ;
2023-11-23 12:08:27 +01:00
config = mkIf ( length cfg > 0 ) {
# For each element (e.g. "ata-WDC_WD40EFRX-68N32N0_WD-WCC7K0CPF0N1") in `cfg` create this config:
# systemd.services."hdparm-ata-WDC_WD40EFRX-68N32N0_WD-WCC7K0CPF0N1" = {
# description = ...;
# wantedBy = ...;
# ...
# };
systemd . services = builtins . listToAttrs ( builtins . map ( id : {
name = " h d p a r m - ${ id } " ;
value = {
description = " S p i n d o w n i n a c t i v e H D D " ;
wantedBy = [ " m u l t i - u s e r . t a r g e t " ] ;
serviceConfig . Type = " o n e s h o t " ;
script = ''
2023-11-23 13:04:07 +01:00
set - eu - o pipefail
# Get IDE power mode status and remove any whitespace characters for easier pattern matching.
result = " $ ( ${ pkgs . hdparm } / b i n / h d p a r m - C / d e v / d i s k / b y - i d / ${ id } | ${ pkgs . coreutils } / b i n / t r - d ' [ : s p a c e : ] ' ) "
case " ' ' ${ result } " in
* ' /dev/disk/by-id / $ { id }: drivestateis:standby'* | * ' /dev/disk/by-id / $ { id }: drivestateis:sleeping'* )
printf ' % s \ n' ' Disk is in standby or sleeping . '
# Don't set idle timeout as this would spin up the disk.
# It has already been set before, otherwise the disk would be spinning.
; ;
* ' /dev/disk/by-id / $ { id }: drivestateis:active/idle'* )
printf ' % s \ n' ' Disk is active . Set idle timeout to 10 minutes . '
$ { pkgs . hdparm } /bin/hdparm - S 24 /dev/disk/by-id / $ { id }
; ;
* ' /dev/disk/by-id / $ { id }: drivestateis:unknown'* )
printf ' % s \ n' ' Disk is not supported by hdparm . '
exit 1
; ;
* )
printf ' % s % s \ n' ' Unknown hdparm output : ' " ' ' ${ result } "
exit 1
; ;
esac
2023-11-23 12:08:27 +01:00
'' ;
} ;
} ) cfg ) ;
2023-10-03 12:48:52 +02:00
} ;
}