Protecting My Home Server's Power Button with molly-guard and acpid

A while ago I took one of the old desktop computers in the house and turned it into a basic home server. Right now its main functions are printing and file storage, and most of the time it can be turned off. To make this easy, I’ve kept the default behavior for the power button to shut down the system.

But just shutting down a server like this is risky. What if someone is still logged in and working on something? That work would be lost if the server was shut down unexpectedly.

Guarding Shutdown Commands with molly-guard

The molly-guard package is available in Debian (and derivative distributions such as Ubuntu). It offers some protection against accidental shutdown by replacing the shutdown, poweroff, and similar commands with “guarded” versions. The “guards” are a series of scripts located in /etc/molly-guard/run.d/, and if one of them fails (exits with a nonzero exit code), the operation is canceled.

By default, there are only two basic scripts:

  • The 10-print-message script displays a customizable message.
  • The 30-query-hostname script tries to detect an SSH session and prompts for the hostname as confirmation.

Note that the numeric prefixes control execution order. The scripts are run in order of filename, and if one fails the rest are not run. I wanted my guards to run after the message is displayed but before the hostname prompt, so I named my scripts starting with 20-.

First, I wrote a script to check for active terminal sessions:

#!/bin/sh

ACTIVE_TERMINALS=$(who | wc -l)
CURRENT_TERMINALS=$(who -m | wc -l) # 1 if run from a terminal, 0 otherwise

if [ $ACTIVE_TERMINALS -gt $CURRENT_TERMINALS ]; then
  echo 'W: there are other active terminal sessions.' >&2
  exit 1
fi

Then, I wrote a script to check for active print jobs:

#!/bin/sh

# lpstat -u without a user argument lists jobs from all users
# (although this does not appear to be documented)
ACTIVE_PRINT_JOBS=$(lpstat -u | wc -l)

if [ $ACTIVE_PRINT_JOBS -gt 0 ]; then
  echo 'W: there are active print jobs.' >&2
  exit 1
fi

I placed these into /etc/molly-guard/run.d/, making sure to set execute permissions so they would actually run.

Guarding the Power Button with acpid

There’s a problem, though: While molly-guard guards the shutdown commands, it doesn’t do anything with the power button, which was my original goal. This is because the power button is handled (at least on this system) by systemd-logind, which is a separate service.

Fixing this involves two steps:

  1. Stop systemd-logind from handling the power button
  2. Set up acpid to handle the power button by running the (guarded) shutdown command

First, to stop systemd-logind from handling the power button, I edited /etc/systemd/logind.conf. It had some lines that look like this:

...
#HandlePowerKey=poweroff
#HandleSuspendKey=suspend
#HandleHibernateKey=hibernate
...

These commented-out lines show the default behavior. As you might expect, the default behavior for the power button (or “power key” as it’s called here) is to power off the computer.

I uncommented the line and changed the behavior to “ignore”:

HandlePowerKey=ignore

Then, to set up acpid, I first installed the package:

apt install --no-install-recommends acpid

I included the --no-install-recommends option to prevent the acpi-support-base package from being installed. That package includes some default configuration for handling the power button, but the problem with that configuration is that it will defer to systemd-logind if it detects that it’s running. It’s the exact opposite of what I’m trying to do: I specifically want to bypass systemd-logind and have acpid handle it instead.

Without the acpi-support-base package, acpid isn’t configured to do anything. Fortunately, the example configuration does exactly what I need. I simply copied two files from /usr/share/doc/acpid/examples/:

cp /usr/share/doc/acpid/examples/powerbtn /etc/acpi/events/powerbtn
cp /usr/share/doc/acpid/examples/powerbtn.sh /etc/acpi/powerbtn.sh
chmod a+x /etc/acpid/powerbtn.sh # Make the handler script executable

After this, the power button is now guarded.

More Guards?

There are a few other things I haven’t been able to guard yet:

  • File transfers using Samba (I’m not sure how to check open connections to Samba)
  • ZFS scrubs and resilvers (The zpool status command doesn’t offer a machine-readable output option, and it seems I’m not the only one struggling with this)
Reply to this post via e-mail or on: Twitter.
Philip Chung
Philip Chung
Software Developer