refactor(youko): disable HDD spindown from a udev rule

Apply hdparm -B 255 -S 0 whenever a disk appears, which also covers the
re-add after a bridge reset, instead of a boot oneshot plus an hourly
timer. Drop -J 0: hdparm has always aborted it without
--please-destroy-my-drive, and wdidle3 already reads disabled on both
disks (it is persistent firmware state).

This was the last user of mylib.systemdHardening.privilegedOneshot, so
remove the preset and its test group.

Assisted-by: Claude Code (claude-opus-5-5)
This commit is contained in:
Ryan Yin authored and ryan4yin | 二花 committed 2026-11-01 11:42:57 -06:00
1 parent f8ca25a03b
commit 8084f25e6f
4 files changed
+8 -110

No files matched your search

+8 -38
View File
@@ -1,5 +1,6 @@
{ {
config, config,
lib,
pkgs, pkgs,
mylib, mylib,
myvars, myvars,
@@ -13,6 +14,10 @@ let
# disko config does (the /dev/sdX names are not stable). # disko config does (the /dev/sdX names are not stable).
hddPublic = "/dev/disk/by-id/ata-WDC_WD40EJRX-89T1XY0_WD-WCC7K0XDCZE6"; hddPublic = "/dev/disk/by-id/ata-WDC_WD40EJRX-89T1XY0_WD-WCC7K0XDCZE6";
hddEncrypted = "/dev/disk/by-id/ata-WDC_WD40EZRZ-22GXCB0_WD-WCC7K7VV9613"; hddEncrypted = "/dev/disk/by-id/ata-WDC_WD40EZRZ-22GXCB0_WD-WCC7K7VV9613";
hddSerials = lib.concatMapStringsSep "|" (lib.removePrefix "/dev/disk/by-id/ata-") [
hddPublic
hddEncrypted
];
coreModule = mylib.genVmHostModule { coreModule = mylib.genVmHostModule {
inherit pkgs hostName; inherit pkgs hostName;
@@ -105,48 +110,13 @@ in
]; ];
# Keep the bridge powered: a suspended device is what most often triggers a # Keep the bridge powered: a suspended device is what most often triggers a
# reset on resume. # reset on resume. Spindown is another trigger, so turn off APM/standby on the
# HDDs each time they appear (a bridge reset re-adds them).
services.udev.extraRules = '' services.udev.extraRules = ''
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="152d", ATTR{idProduct}=="0567", TEST=="power/control", ATTR{power/control}="on" ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="152d", ATTR{idProduct}=="0567", TEST=="power/control", ATTR{power/control}="on"
ACTION=="add", SUBSYSTEM=="block", ENV{DEVTYPE}=="disk", ENV{ID_SERIAL}=="${hddSerials}", RUN+="${pkgs.hdparm}/bin/hdparm -B 255 -S 0 $devnode"
''; '';
# A spindown/spinup is a common trigger for the bridge resetting, so disable
# APM/standby on the HDDs. sdb in particular has ~1M load cycles (WD
# Intellipark), so stopping the idle head-parking also saves its mechanism.
# Read the settings back into the journal, and re-assert them hourly: the
# bridge or the disk's own firmware can drop them.
systemd.services.hdd-no-spindown = {
description = "Disable APM/standby on the USB HDDs";
after = [ "local-fs.target" ];
wantedBy = [ "multi-user.target" ];
serviceConfig = mylib.systemdHardening.privilegedOneshot // {
Type = "oneshot";
RemainAfterExit = true;
CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ];
PrivateDevices = false;
DeviceAllow = [ "block-sd rw" ];
};
script = ''
for d in ${hddPublic} ${hddEncrypted}; do
echo "== $d =="
# -B 255: APM off; -S 0: no standby timer; -J 0: WD idle3 (Intellipark) off
${pkgs.hdparm}/bin/hdparm -B 255 -S 0 -J 0 "$d" || true
${pkgs.hdparm}/bin/hdparm -B "$d" || true
${pkgs.hdparm}/bin/hdparm -J "$d" || true
${pkgs.hdparm}/bin/hdparm -C "$d" || true
done
'';
};
systemd.timers.hdd-no-spindown = {
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "10min";
OnUnitActiveSec = "1h";
Persistent = true;
};
};
# Watch the disks with SMART. UDMA_CRC_Error_Count growth is the early warning # Watch the disks with SMART. UDMA_CRC_Error_Count growth is the early warning
# for the bridge/link problem. # for the bridge/link problem.
services.smartd = { services.smartd = {
-31
View File
@@ -42,37 +42,6 @@ rec {
]; ];
}; };
# For short-lived root oneshots that perform one narrowly scoped privileged
# operation. Callers add only the capabilities and device access they need.
# ProtectKernelTunables is deliberately unset because it makes /sys read-only.
privilegedOneshot = {
AmbientCapabilities = "";
CapabilityBoundingSet = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
PrivateNetwork = true;
PrivateTmp = true;
ProcSubset = "pid";
ProtectClock = true;
ProtectControlGroups = true;
ProtectHome = true;
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectProc = "invisible";
ProtectSystem = "strict";
RemoveIPC = true;
RestrictAddressFamilies = [ "AF_UNIX" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
SystemCallArchitectures = "native";
UMask = "0077";
};
# Apply a preset at mkOptionDefault priority, so nixpkgs module values win. # Apply a preset at mkOptionDefault priority, so nixpkgs module values win.
mkDefaults = lib.mapAttrs (_: lib.mkOptionDefault); mkDefaults = lib.mapAttrs (_: lib.mkOptionDefault);
} }
@@ -9,17 +9,6 @@ let
"~@clock @cpu-emulation @debug @module @mount @obsolete @privileged @raw-io @reboot @swap" "~@clock @cpu-emulation @debug @module @mount @obsolete @privileged @raw-io @reboot @swap"
]; ];
}; };
privilegedOneshot = {
CapabilityBoundingSet = "";
DeviceAllow = null;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateNetwork = true;
ProtectKernelTunables = null;
ProtectSystem = "strict";
RestrictAddressFamilies = [ "AF_UNIX" ];
};
in in
{ {
# Only 80/443. # Only 80/443.
@@ -32,12 +21,4 @@ in
whisper-asr = sandbox // { whisper-asr = sandbox // {
PrivateDevices = false; PrivateDevices = false;
}; };
privilegedOneshot = {
hdd-no-spindown = privilegedOneshot // {
CapabilityBoundingSet = [ "CAP_SYS_RAWIO" ];
DeviceAllow = [ "block-sd rw" ];
PrivateDevices = false;
};
};
} }
@@ -12,23 +12,6 @@ let
lib.genAttrs names ( lib.genAttrs names (
n: lib.getAttrs keys outputs.nixosConfigurations.${host}.config.systemd.services.${n}.serviceConfig n: lib.getAttrs keys outputs.nixosConfigurations.${host}.config.systemd.services.${n}.serviceConfig
); );
privilegedKeys = [
"CapabilityBoundingSet"
"DeviceAllow"
"NoNewPrivileges"
"PrivateDevices"
"PrivateNetwork"
"ProtectKernelTunables"
"ProtectSystem"
"RestrictAddressFamilies"
];
privilegedUnit =
host: name:
let
cfg = outputs.nixosConfigurations.${host}.config.systemd.services.${name}.serviceConfig;
in
lib.genAttrs privilegedKeys (key: cfg.${key} or null);
in in
units "youko" [ units "youko" [
"caddy" "caddy"
@@ -36,8 +19,3 @@ units "youko" [
"vmalert-homelab" "vmalert-homelab"
] ]
// units "aquamarine" [ "whisper-asr" ] // units "aquamarine" [ "whisper-asr" ]
// {
privilegedOneshot = {
hdd-no-spindown = privilegedUnit "youko" "hdd-no-spindown";
};
}