Keep computer suspended between determined hours

I wanted to make a computer “refuse to work” if it’s too late.

The poweroff approach

We could do it with a simple poweroff on a cronjob like this:

* 0-5,23 * * * root /usr/sbin/poweroff

This means: run poweroff as the user root daily at every minute from 23:00 to 05:59. Or in other words, between 23:00 and 06:00, if the computer is on, it will turn off at the next round minute.

This method surely could work. However, I figured that the destructive approach of turning off the computer all of a sudden could be inconvenient. For some caution, I wanted to suspend instead. This way, even if I was working on something, I could finish it the next day.

The simple suspension approach

For that we could just change the poweroff command for systemctl suspend, but I wasn’t too happy with the idea. Why?

Well, in our first approach, the computer is turned off. If we turn it on again, we’ll have to wait some seconds for the boot and then login. In the meanwhile, your “grace minute” until the next firing of poweroff is running out, and not many seconds are left.

On the second approach, the computer is suspended. If we wake it up, we are just a login screen away from our previous state. We’ll have most of the “grace minute” left and in this time can be tempted to disable the job. As cron can’t go more granular than minutes, we are stuck.

The better suspension approach

So my goal was to automatically suspend the computer as soon as possible if:

  • We turn on the computer during the determined time of the day.
  • We wake up the computer during the determined time of the day.
  • We are using the computer and we entered the determined time of the day.

For that, I wrote this script:

#!/usr/bin/env bash

if [[ $# -ne 2 ]]; then
echo "Wrong number of arguments."
exit 1
fi
readonly ge="$1"
readonly le="$2"

readonly hour="$(date +%-H)"
while [[ $hour -ge $ge || $hour -le $le ]]; do
systemctl suspend
sleep 5s
done

It will check if the current time is between the two determined hours, and if so, suspend the computer. When waking up, it will do the check again. If we are still between those hours, the computer is put back to suspension.

So now the check is fired at every minute (as we had with cron), but also just after waking up from suspension. This fixes the problem I described.

For scheduling this script, I also chose cron. Cron gives us less control of our jobs than systemd, and this is desired for this use case. With systemd, we might have been tempted to run systemctl stop on this job.

So, I placed the script on /usr/local/sbin/auto_suspend and dropped this on /etc/cron.d/auto_suspend:

* 0-5,23 * * * root flock -n /var/lock/auto_suspend.lock /usr/local/sbin/auto_suspend 23 5

This is very similar to the previous cronjob, with the difference that instead of scheduling poweroff, we are scheduling our script and using flock. Flock prevents that we run more than one instance of the command, which is relevant since our cronjob runs every minute.

Notice that our script takes two arguments, the start and the end of the “refusal to work” time. This puts all the time information in the same place, the cronjob, making it much easier to change later.