Suspend NAS when idle
My NAS with 4TB RAID-5 storage consumes round about 40-45 Watt. Not very much for a "full featured" Athlon X2 4600+ Linux systems, but running 24/7 it produces around 70 EUR per year on energy costs, so I would like to suspend it to ram when idleing. But since I don't want to press a button or running wake-on-lan manually when watching a video, my HTPCs (a Mac Mini running Ubuntu and a Raspberry Pi running XBian) should wake it automatically when accessing the media directory.
So, let's start with the server, I'll write a second post for the client part:
I want it to suspend to ram, but not if
- a user is connected to the system using sshfs
- a user has been connected within the last 30 minutes (it's likely that he will come back, soon)
- a screen session is active (for example when reencoding videos in background)
- the system is active for less than 30 minutes
Required tools:
- ethtool
- pm-utils
First of all, ensure that the system can be woken up. There are a lot of manuals how to do this, but I like this one most because it works for every attached network device:
Create "/etc/udev/rules.d/50-wol.rules" with the following contents:
ACTION=="add", SUBSYSTEM=="net", KERNEL=="eth*", RUN+="/usr/bin/ethtool -s %k wol g"
Next, we need to know when a user logged on or of. I've implemented this by storing the username in a file named by the process pid in /var/run/system-login/active/ and /var/run/system-login/active/ using pam_exec.so. First, create "/usr/local/sbin/system-login" and make the file executable:
#!/bin/bash # Check if process is alive using pgrep -P $pid ppid="$(ps -p $$ -o ppid= | tr -d ' ')" activepath="/var/run/system-login/active" inactivepath="/var/run/system-login/inactive" case "$PAM_TYPE" in 'open_session') test -d "$activepath" || mkdir -p "$activepath" echo "$PAM_USER" > "$activepath/$ppid" ;; 'close_session') test -d "$inactivepath" || mkdir -p "$inactivepath" test -e "$activepath/$ppid" && mv "$activepath/$ppid" "$inactivepath/$ppid" ;; esac
Next, add this to the according files under /etc/pam.d:
session optional pam_exec.so quiet /usr/local/sbin/system-login
Using Arch Linux, this would be /etc/pam.d/system-login. On Ubuntu, you can use /etc/pam.d/common-session or append it directly to /etc/pam.d/sshd. Now, reboot, login and ensure that /var/run/system-login/active contains nothing else but your current login. Otherwise you would have to choose another pam.d file or add additional tests to the script.
But how to check how long the system is active? "uptime" tells us nothing about resumes from suspend, so we have to add this information manually. Create "/etc/pm/sleep.d/50update-last-resume" and make it executable:
#!/bin/sh if [ -n "$1" ] && ([ "$1" = "resume" ] || [ "$1" = "thaw" ]); then touch "/var/run/last-resume" fi
Run "pm-suspend", resume the system and check whether a file called /var/run/last-resume has been created.
Finally, we need a script that checks all conditions and tells us if we should suspend or not. The following code, stored in /usr/local/sbin/should-suspend (and made executable, of course) will do this:
#!/bin/bash result=0 timeout=30 NOW=`date +%s` # Check system uptime test -e /var/run/last-resume || touch /var/run/last-resume if find /var/run/ -maxdepth 1 -type f -name last-resume -cmin -$timeout | grep -q .; then let age=(NOW-$(stat -c %Z /var/run/last-resume))/60 echo "uptime to short ($age min)" result=1 fi # Check for active screen sessions if pgrep -l '^screen$'; then result=1 fi # Check for active user if [ -d "/var/run/system-login/active" ]; then for n in $(find /var/run/system-login/active -type f -printf "%f\n"); do if pgrep -P "$n" > /dev/null; then result=1 filename="/var/run/system-login/active/$n" let age=(NOW-$(stat -c %Z "$filename"))/60 echo "active login: $n (`cat "$filename"`, $age min)" else rm "/var/run/system-login/active/$n" fi done fi # Check for logouts that where at least $timeout minutes ago if [ -d "/var/run/system-login/inactive" ]; then for n in $(find /var/run/system-login/inactive -cmin -$timeout -type f -printf "%f\n"); do result=1 filename="/var/run/system-login/inactive/$n" let age=(NOW-$(stat -c %Z "$filename"))/60 echo "inactive login: $n (`cat "$filename"`, $age min)" done fi exit $result
If everything's fine, this script will print nothing and exit with 0, otherwise it will exit with 1 and print the reasons.
Last, add a cronjob in /etc/cron.d/suspend-on-idle that check's every five minutes if the conditions are met and call pm-suspend:
SHELL=/bin/bash PATH=/sbin:/bin:/usr/sbin:/usr/bin MAILTO=root */5 * * * * root /usr/local/sbin/should-suspend > /dev/null && pm-suspend
This is it. Let the system idle for a while, check if it suspends and if you can wake it using wake-on-lan.
Please note: Since I'm having a Linux-only enviroment, this will only work with SSHFS/SFTP/SCP mounts, not with SMB. I'm not sure if it is possible to adapt this technique to these protocols. Maybe SMB fires a PAM event, than it should be possible to bind system-login to this.
Comments
Martin Stenzel (not verified)
19. June 2016 - 22:48
Permalink
Great page, works very nice.
Great page, works very nice.
Two comments:
1. When running lightdm display manager user "lightdm" will prevent suspend.
Modification of the file "system-login" will help
....
case "$PAM_TYPE" in
'open_session')
test -d "$activepath" || mkdir -p "$activepath"
# modification needed since lightdm display manager might be active...
if [ "$PAM_USER" != "lightdm" ]; then
echo "$PAM_USER" > "$activepath/$ppid"
fi
;;
...
2. "uptime to short" in "should-suspend" should read "uptime too short"
Thanks again for sharing your code!
Martin.