For a long time I've not had any network attached storage at home, because it's offgrid and power budget didn't allow it. But now I have 16 terabytes of network attached storage, that uses no power at all when it's not in use, and automatically spins up on demand.
I used a USB hub with per-port power control. But even with a USB drive's port powered down, there's a parasitic draw of around 3 watts per drive. Not a lot, but with 4 drives that's more power wasted than leaving a couple of ceiling lights on all the time. So I put all the equipment behind a relay too, so it can be fully powered down.
I'm using systemd for automounting the drives, and have it configured to power a drive's USB port on and off as needed using uhubctl. This was kind of tricky to work out how to do, but it works very well.
Here's the mount unit for a drive, media-joey-passport.mount:
[Unit]
Description=passport
Requires=startech-hub-port-4.service
After=startech-hub-port-4.service
[Mount]
Options=noauto
What=/dev/disk/by-label/passport
Where=/media/joey/passport
That's on port 4 of the USB hub, the startech-hub-port-4.service unit file is this:
[Unit]
Description=Startech usb hub port 4
PartOf=media-joey-passport.mount
[Service]
Type=oneshot
RemainAfterExit=true
ExecStart=/usr/sbin/uhubctl -a on -p 4 ; /bin/sleep 20
ExecStop=/usr/sbin/uhubctl -a off -p 4
The combination of PartOf with Requires and After in these units makes systemd start the port 4 service before mounting the drive, and stop it after unmounting. This was the hardest part to work out.
The sleep 20
is a bit unfortunate, it seems that it can take a few seconds for
the drive to power up enough for the kernel to see it, and so without that the
mount can fail, leaving the drive powered on indefinitely. Seems there
ought to be a way to declare an additional dependency and avoid needing that sleep?
Update: See my comment below for a better way.
Finally, the automount unit for the drive, media-joey-passport.automount:
[Unit]
Description=Automount passport
[Automount]
Where=/media/joey/passport
TimeoutIdleSec=300
[Install]
WantedBy=multi-user.target
The TimeoutIdleSec makes it unmount after around 5 minutes of not being used, and then its USB port gets powered off.
I decided to not automate the relay as part of the above, instead I typically turn it on for 5 hours or so, and use the storage whenever I want during that window. One advantage to that is cron jobs can't spin up the drives in the early morning hours.
Your .mount unit can use
Requires=dev-disk-by\x2dlabel-passport.device
.(Actually I was quite sure that .mount units automatically depend on their What= device in any case, but I guess only an After= is implicit but a Requires= isn't.)
I think it would be simpler to use
StopWhenUnneeded=yes
in the .service (once you have the aforementioned Requires=dev-disk in your .mount unit). Such a combination might be more reliable as well.startech-usb-hub@.service
which uses%i
in place of the port number everywhere.Hi Joey,
Could you also elaborate on how you monitor your USB drive health, considering kernel doesn't seem to allow SAT ATA passthrough commands when using 'uas' driver.
In systemd unit this line probably doesn't mean what you seem to think it does:
"/bin/sleep 20" there is probably irrelevant - a comment in an ini file.
Unless debian patches systemd that way, whole ExecStart= line isn't passed to shell anyway, so if that part was passed to command, it'd probably just exit on parsing options.
Something like this should work though:
No, my syntax is correct and works. According to systemd.syntax(7):
Nothing about use of those characters further into a line.
(Some of systemd's docs do contain examples of using sh -c and escaping semicolons, so perhaps this has changed since earlier versions. Multiple ExecStart lines is another way to run multiple commands.)
But in fact, the example I gave works. You can see the 20 second delay in the journal:
@grawity, I tried the Requires on the device label, but it does not avoid the problem:
Ah, I think I see why. After the usb port is shut down, udev keeps the disk device files in place. Attempting to access the device then leads to an IO error, and then udev notices the device is gone and removes its files. So systemd's dependencies are working, but it's seeing stale information.
Perhaps this is a bug in udev, that it's not noticing the usb port powered down. It seems to not get any kernel event for it at all, according to
udevadm monitor
.I thought maybe a different workaround would be to dd one byte from the disk after powering it down, or
udevadm trigger
the device manually. Unfortunately, the kernel auto-powers the usb port back up when either of those is done. So I'll stick with the sleep for now..No, it means exactly that. Multiple commands in ExecStart= separated by a standalone
;
token have been supported for a very long time. (Though they don't always make sense logically – e.g. if you have Type=forking, which command is supposed to be the daemon? why aren't they ExecStartPre's instead? – but in aType=oneshot
this usage is fine.)Udev maintains
/dev/disk/*
symlinks and systemd's .device units, but it relies 100% on receiving the kernel's uevents to trigger the maintenance. (For that matter, the actual/dev/sd*
nodes are created by the kernel itself via devtmpfs, not by udev anymore.)Therefore that's a kernel problem. (Or... maybe just the way uhubctl works? Does the kernel even know that the port is now powered off? I think it doesn't, because uhubctl sort of bypasses the whole USB stack.)
I wonder what happens if you try to fake a kernel uevent using
udevadm trigger --action=remove /sys/<sysfs_path>
after the poweroff. (Orecho remove > /sys/<sysfs_path>/uevent
.) That won't remove the device from kernel, just poke udev about its supposed removal.Aha. That's unfortunate. (Even though "the API can always be enlarged" I wouldn't hold my breath...)
@grawity hmm, that's promising, the sysfs uvent file did not trigger udev, but "udevadm trigger --action=remove /sys/class/block/sdb1" does remove links to it in /dev/disk/by-label/, though /dev/sdb1 remains present.
And yeah, systemd's dependency on the device does work properly then, it delays mounting until the drive has spun up.
Ah, even easier, "udevadm trigger --action=remove /dev/disk/by-label/passport" does the same thing without needing to find the sysfs path.
So, I've changed the service file for the hub port to look like this:
/bin/sh actually needed now since a failure of udevadm due to the label not existing etc needs to be ignored.
Kind of ugly that the service file for the hub port needs to know the label of the disk, but since I'm generating these service files not using systemd's limited templates, but from Haskell code, it was easy to extend to include that.
When you mention relay, do you mean the electromechanical devices? If so, do you measure the current consumption of the relay coil when it's energized?
Also, what is the relay doing for you if it's manually actuated? Is it in place for future automation? It seems the relay could otherwise safely be a regular mechanical switch in this relatively low power application.
Yes, it's a good old fashioned relay, but it's under the computer's control.
For example, this relay https://www.sparkfun.com/products/13815 can be controlled by a GPIO port, consumes 0.6 watts of power to run and can switch 15 amps of AC current. I'm happily using several of them for other projects.
My USB hub is actually powered by 24V DC, which comes from my solar charge controller's load port, which is also switched by computer control. That line also powers a more beefy 24V industrial relay that I had lying around, which can switch a lot more power but does consume 5 watts when run. I'll probably downgrade that relay at some point but an extra 5 watts when the drives are running is not a big deal.
udisksctl power-off
to turn off the USB drives safely before shutting off power to their ports? That uses the same mechanisms as the "safely eject" GUI options and I think that would safer and might solve the timing issues.@pabs3,
udisksctl power-off --block-device /dev/disk/by-label/passport
does cause udev to remove the device file, it does not seem any better thanudevadm trigger --action=remove
in this situation though, because systemd has already unmounted the disk before that runs.The only difference I notice is that it disconnects the hub port, but that leaves it powered on, so uhubctl is still needed to power off.
Unless it's better at getting caches flushed to the disk or something like that?
The documentation (quoted below) is a bit vague about exactly what it does, but it seems like does more than just disabling the port and probably does better at flushing caches than just unmounting.
If i just switch off ports via uhubctl and not bother removing the /dev nodes for it, then something in the system will later access the /dev node, which leads to I/O error, removal of the dev node from the file system, but also in powering on the port again and then recreation of the dev nodes. Aka: not good to ensure that ports will be powered off reliably. For example on my system, udisksd itself wakes up disks something like every 10 minutes for SMART check.
When using udevadm or any other tool to just remove the /dev nodes, then the risk of unintentionally powering up the port/disk still exists when there are any appliation with still open files to the dev nodes i think. Worse yet, when all dev nodes are removed and one wants to power the port up again with uhubctl, then the system will not recreate the dev nodes - because the kernel never saw the disk to have gone away. Only the dev nodes where removed.
udiskctl actually does the correct job: It removes the kernel notion of the disk, which then removes the dev nodes. Example:
udisks-Message: 22:56:40.318: Successfully sent SCSI command SYNCHRONIZE CACHE to /dev/sdd udisks-Message: 22:56:41.088: Successfully sent SCSI command START STOP UNIT to /dev/sdd udisks-Message: 22:56:41.174: Powered off /dev/sdd - successfully wrote to sysfs path /sys/devices/pci0000:00/0000:00:08.1/0000:38:00.4/usb6/6-1/6-1.3/6-1.3.1/remove
So the correct solution for me is to use udiskctl to remove dev nodes, then power down port with uhubctl. After powering up with uhubctl the kernel will correctly recreate the dev nodes. And in between, the only remaining risk for unintentionally waking up the disk is anything in the kernel resestting the whole hub.
Startech ST4200USBM.
But uhubctl's home page has a good list of all known usable hubs, many much cheaper.