How to configure NixOS to allow a program to trigger shutdown?

Context

I’m writing a Rust program for an art installation. The gallery will send a packet via TCP/IP to our installation a few minutes before the power goes off in order to allow for a graceful exit and shutdown.

To achieve the shutdown part, I’m doing something along these lines in my Rust software:

Command::new("shutdown").arg("now").output().unwrap()

Which should be equivalent to the program calling the shutdown command with the now argument.

Problem

However, I notice that when triggered by the program, an authentication prompt is presented (rather than just shutting down immediately as when typed into the console).

After doing some research, it seems that I should be able to add a “polkit” rule that allows for this. Specifically, I came across the following example of how to so:

polkit.addRule(function(action, subject) {
    if (action.id == "org.freedesktop.login1.reboot" ||
        action.id == "org.freedesktop.login1.reboot-multiple-sessions" ||
        action.id == "org.freedesktop.login1.power-off" ||
        action.id == "org.freedesktop.login1.power-off-multiple-sessions") {
        if (subject.isInGroup("power")) {
            return polkit.Result.YES;
        } else {
            return polkit.Result.AUTH_ADMIN;
        }
    }
});

Attempted Solution

I tried adding this rule to my nix config like so:

  security.polkit = {
    extraConfig = ''
      polkit.addRule(function(action, subject) {
          if (action.id == "org.freedesktop.login1.reboot" ||
              action.id == "org.freedesktop.login1.reboot-multiple-sessions" ||
              action.id == "org.freedesktop.login1.power-off" ||
              action.id == "org.freedesktop.login1.power-off-multiple-sessions") {
              if (subject.isInGroup("power")) {
                  return polkit.Result.YES;
              } else {
                  return polkit.Result.AUTH_ADMIN;
              }
          }
      });
    '';
  };

While I can see that my rule does get called (by logging inside the function), the action never appears to have the “power-off” or “reboot” IDs. If I log the action ID when attempting to shutdown from a program, I get the following IDs:

Feb 19 17:22:13 mindtree polkitd[1256]: <no filename>:6: action.id: org.freedesktop.systemd1.manage-units
Feb 19 17:22:13 mindtree polkitd[1256]: <no filename>:6: action.id: org.freedesktop.systemd1.manage-unit-files

Is it safe to simply check for this ID instead? I’m worried that the ID may represent a broader set of actions and that I might be making my system unnecessarily vulnerable by doing so.

Alternatively, is there a better way I might trigger a shutdown from a user-run program? E.g. is there some way I can give permissions to the executable itself?

Any help greatly appreciated!

2 Likes

This seems to have worked nicely:


  # Allow the user run a program to poweroff the system.
  security.polkit = {
    extraConfig = ''
      polkit.addRule(function(action, subject) {
          if (action.id == "org.freedesktop.systemd1.manage-units" ||
              action.id == "org.freedesktop.systemd1.manage-unit-files") {
              if (action.lookup("unit") == "poweroff.target") {
                  return polkit.Result.YES;
              }
          }
      });
    '';
  };

However before copy-pasting this, future readers may wish to also check that their subject is a part of a suitably privileged group before returning YES (this doesn’t matter so much in our gallery installation).

2 Likes