Disable suspend if SSH sessions are active

My desktop computer, which runs GNOME, is configured to suspend on 20 minutes of inactivity (desired behavior). However, at various time I tend to remote to it from my laptop for development.

What’s the best approach to disable suspend of computer while SSH sessions are active (i.e., not idle)?

What would be the NixOS way to configure it?

1 Like

Seeing as nobody knows, then i will theory-ize , then you going to have ‘tickle’ gnome in some, more than likekly with a dbus message, to tell the gnome suspend function that ‘something is happening’'.

https://wiki.debian.org/Suspend

seems gnome does

Disable suspend and hibernation

For systems which should never attempt any type of suspension, these targets can be disabled at the systemd level with the following:

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

To re-enable hibernate and suspend use the following command:

sudo systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target

so , maybe something on a cronjob, which checks if users are still logged in, you may need ignore users logged into actually local tty’s and local xsessons.

in the system profile, that runs when all users logging in do.

sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target

on a cron job…

last | grep "still logged in" 
if [[ $? -eq 1 ]]
then
#To re-enable hibernate and suspend use the following command:
sudo systemctl unmask sleep.target suspend.target hibernate.target hybrid-sleep.target
fi

which renables suspend service , when all users are logged out , again don’t forget to exclude local users on tty’s :-).

have fun.

This post was brought you by the ACME instant shell scripting service. Thank you for using ACME.

image

2 Likes

I get this:

[b0ef@nixos:~]$ sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target
[sudo] password for b0ef:
Failed to mask unit: File /etc/systemd/system/sleep.target already exists and is a symlink to /nix/store/9gzw98jc64qkwd17a6qqm63w25zysi57-systemd-253.6/example/systemd/system/sleep.target.

In case it helps anyone else, this is how I solved it this afternoon. Could be cleaned up a bit, but it seems to work.

I put this in an ssh.nix file and imported into my configuration.nix. It works by forking a program that just sleeps forever upon first SSH session login and killing it when the last session is closed.

{ config, options, lib, pkgs, ... }:

let
  # Prevent sleeping on active SSH
  sleep_script = pkgs.writeScript "infinite-sleep"
  ''
    #!/bin/sh

    echo $$ >/tmp/ssh_sleep_block.pid
    sleep infinity
  '';

  sleep_wrapper = pkgs.writeScript "sleep-wrapper"
  ''
    #!/bin/sh

    setsid systemd-inhibit --what=sleep --why="Active SSH session" --mode=block ${sleep_script} 0>&- &> /tmp/inhibit.out &
  '';

  ssh_script = pkgs.writeScript "ssh-session-handler"
  ''
    #!/bin/sh
    #
    # This script runs when an ssh session opens/closes, and masks/unmasks
    # systemd sleep and hibernate targets, respectively.
    #
    # Inspired by: https://unix.stackexchange.com/a/136552/84197 and
    #              https://askubuntu.com/a/954943/388360

    num_ssh=$(netstat -nt | awk '$4 ~ /:22$/ && $6 == "ESTABLISHED"' | wc -l)

    # echo "User id is $UID, num_ssh is $num_ssh, pam type $PAM_TYPE" > /tmp/ssh_user

    case "$PAM_TYPE" in
        open_session)
            if [ "$num_ssh" -gt 1 ]; then
                exit
            fi

            logger "Starting sleep inhibitor"
            ${sleep_wrapper}
            logger "Sleep inhibitor started with PID `cat /tmp/ssh_sleep_block.pid`"
            ;;

        close_session)
            if [ "$num_ssh" -ne 0 ]; then
                exit
            fi

            logger "Killing sleep inhibitor PID `cat /tmp/ssh_sleep_block.pid`"
            kill -9 `cat /tmp/ssh_sleep_block.pid` && rm /tmp/ssh_sleep_block.pid
            ;;

        *)
            exit
    esac

  '';
in {
  security.pam.services.sshd.text = pkgs.lib.mkDefault(
    pkgs.lib.mkAfter
    "# Prevent sleep on active SSH\nsession optional pam_exec.so quiet ${ssh_script}"
  );
}
2 Likes

Thanks for a fantastic answer @cbrauchli! I am new to Nix & NixOS and learned a lot by digging into how this works.

I made a couple slight modifications to your expression. I found that the PID of the sleep inhibitor process was not being logged because /tmp/ssh_sleep_block.pid does not exist at the time of logging. I solved this by creating a named pipe in ssh_script that sleep_script writes to. I don’t know if this is the best solution, but it works for me! I also found that the setsid command is not necessary because the process is run in the background anyway. My version (including a few renamed variables and the such) is below, but thanks again for solving this problem for me!

# This expression was written by `cbrauchli` at https://discourse.nixos.org/t/disable-suspend-if-ssh-sessions-are-active/11655/4
# with minor modifications by Dominic Mayhew

{ config, options, lib, pkgs, ... }:

let
  PID_PATH = "/tmp/ssh_sleep_block.pid";
  PID_PIPE = "pid_pipe";

  # Prevent sleeping on active SSH
  sleep_script = pkgs.writeScript "infinite-sleep"
  ''
    #!/bin/sh

    echo $$ >${PID_PATH}
    echo $$ >${PID_PIPE}
    sleep infinity
  '';

  inhibit_script = pkgs.writeScript "inhibit_script"
  ''
    #!/bin/sh

    systemd-inhibit --what=sleep --why="Active SSH session" --mode=block ${sleep_script} 0>&- &> /tmp/inhibit.out &
  '';

  ssh_script = pkgs.writeScript "ssh-session-handler"
  ''
    #!/bin/sh
    #
    # This script runs when an ssh session opens/closes, and masks/unmasks
    # systemd sleep and hibernate targets, respectively.
    #
    # Inspired by: https://unix.stackexchange.com/a/136552/84197 and
    #              https://askubuntu.com/a/954943/388360

    num_ssh=$(netstat -nt | awk '$4 ~ /:22$/ && $6 == "ESTABLISHED"' | wc -l)

    # echo "User id is $UID, num_ssh is $num_ssh, pam type $PAM_TYPE" > /tmp/ssh_user

    case "$PAM_TYPE" in
        open_session)
            if [ "$num_ssh" -gt 1 ]; then
                exit
            fi

            logger "Starting sleep inhibitor"
            mkfifo ${PID_PIPE}
            ${inhibit_script}
            logger "Sleep inhibitor started with PID $(cat ${PID_PIPE})"
            rm ${PID_PIPE}
            ;;

        close_session)
            if [ "$num_ssh" -ne 0 ]; then
                exit
            fi

            logger "Killing sleep inhibitor PID $(cat ${PID_PATH})"
            kill -9 $(cat ${PID_PATH}) && rm ${PID_PATH}
            ;;

        *)
            exit
    esac

  '';
in {
  security.pam.services.sshd.text = pkgs.lib.mkDefault(
    pkgs.lib.mkAfter
    "# Prevent sleep on active SSH\nsession optional pam_exec.so quiet ${ssh_script}"
  );
}