Skip to content

Instantly share code, notes, and snippets.

@mattdenner
Last active November 28, 2024 06:40
Show Gist options
  • Save mattdenner/befcf099f5cfcc06ea04dcdd4969a221 to your computer and use it in GitHub Desktop.
Save mattdenner/befcf099f5cfcc06ea04dcdd4969a221 to your computer and use it in GitHub Desktop.
Suspend and then hibernate after 60 minutes

I found a post about suspending and then going into hibernate that included a really clever script. Turns out that with NixOS this is even esaier to coordinate as you have systemd so can have a before and after service. I just include this in my /etc/nixos/configuration.nix file and nixos-rebuild; then a systemctl suspend or a close of the lid will cause the hibernate timer to be set.

{ config, pkgs, ... }: let
hibernateEnvironment = {
HIBERNATE_SECONDS = "3600";
HIBERNATE_LOCK = "/var/run/autohibernate.lock";
};
in {
systemd.services."awake-after-suspend-for-a-time" = {
description = "Sets up the suspend so that it'll wake for hibernation";
wantedBy = [ "suspend.target" ];
before = [ "systemd-suspend.service" ];
environment = hibernateEnvironment;
script = ''
curtime=$(date +%s)
echo "$curtime $1" >> /tmp/autohibernate.log
echo "$curtime" > $HIBERNATE_LOCK
${pkgs.utillinux}/bin/rtcwake -m no -s $HIBERNATE_SECONDS
'';
serviceConfig.Type = "simple";
};
systemd.services."hibernate-after-recovery" = {
description = "Hibernates after a suspend recovery due to timeout";
wantedBy = [ "suspend.target" ];
after = [ "systemd-suspend.service" ];
environment = hibernateEnvironment;
script = ''
curtime=$(date +%s)
sustime=$(cat $HIBERNATE_LOCK)
rm $HIBERNATE_LOCK
if [ $(($curtime - $sustime)) -ge $HIBERNATE_SECONDS ] ; then
systemctl hibernate
else
${pkgs.utillinux}/bin/rtcwake -m no -s 1
fi
'';
serviceConfig.Type = "simple";
};
}
@aou
Copy link

aou commented Apr 17, 2023

this is awesome! suspend-then-hibernate from systemd was first broken by a weird spat between the maintainer and everyone else, then kinda stopped working entirely for me. This config works, with one minor addition you might want to add at some point -- if you have an nvidia card and use the power management systemd services, then hibernate-after-recovery should come after "nvidia-resume.service".

@HuzaifaTP
Copy link

I tested it on my nixos setup and works as intended. Many thanks 😁. Was curious as to why before it goes to hibernation it needs to wake up first from suspend.

@HuzaifaTP
Copy link

HuzaifaTP commented Dec 16, 2023

A small modification on my end in case anyone is interested, here it will only go into hibernation from suspension IF it is not plugged into power source:

{ config, pkgs, ... }:

let
  hibernateEnvironment = {
    HIBERNATE_SECONDS = "10";
    HIBERNATE_LOCK = "/var/run/autohibernate.lock";
  };
in {

  systemd.services."awake-after-suspend-for-a-time" = {
    description = "Sets up the suspend so that it'll wake for hibernation only if not on AC power";
    wantedBy = [ "suspend.target" ];
    before = [ "systemd-suspend.service" ];
    environment = hibernateEnvironment;
    script = ''
      if [ $(cat /sys/class/power_supply/AC/online) -eq 0 ]; then
        curtime=$(date +%s)
        echo "$curtime $1" >> /tmp/autohibernate.log
        echo "$curtime" > $HIBERNATE_LOCK
        ${pkgs.utillinux}/bin/rtcwake -m no -s $HIBERNATE_SECONDS
      else
        echo "System is on AC power, skipping wake-up scheduling for hibernation." >> /tmp/autohibernate.log
      fi
    '';
    serviceConfig.Type = "simple";
  };

  systemd.services."hibernate-after-recovery" = {
    description = "Hibernates after a suspend recovery due to timeout";
    wantedBy = [ "suspend.target" ];
    after = [ "systemd-suspend.service" ];
    environment = hibernateEnvironment;
    script = ''
      curtime=$(date +%s)
      sustime=$(cat $HIBERNATE_LOCK)
      rm $HIBERNATE_LOCK
      if [ $(($curtime - $sustime)) -ge $HIBERNATE_SECONDS ] ; then
        systemctl hibernate
      else
        ${pkgs.utillinux}/bin/rtcwake -m no -s 1
      fi
    '';
    serviceConfig.Type = "simple";
  };

}

@verymilan
Copy link

Very nice! Note for the 13'' AMD Framework laptop: AC needs to be ACAD :)

@jyap808
Copy link

jyap808 commented Nov 10, 2024

This works but I did some more reading and testing.

It looks like hibernate-then-suspend was broken for a while in 2022 in systemd but they have since fixed it. This has caused a lot of confusion.

You can add this:

systemd.sleep.extraConfig = ''
    HibernateDelaySec=60min
  '';

systemctl suspend will still just Suspend so the default lid action will still be to Suspend. systemctl suspend-then-hibernate works with the configuration setting in place.

To then change the default lid behavior, you then need to:

  services.logind.lidSwitch = "suspend-then-hibernate";

@HuzaifaTP
Copy link

@jyap808 thats great news ! thanks for the update, will try it out :)

@jyap808
Copy link

jyap808 commented Nov 11, 2024

To fill in all the background to what I found out, it was specifically fixed in Systemd v253 (released February 15, 2023 but depends on when your distro updated to it).

        * systemd-sleep 'HibernateDelaySec=' setting is changed back to
          pre-v252's behaviour, and a new 'SuspendEstimationSec=' setting is
          added to provide the new initial value for the new automated battery
          estimation functionality. If 'HibernateDelaySec=' is set to any value,
          the automated estimate (and thus the automated hibernation on low
          battery to avoid data loss) functionality will be disabled.

Release page: https://github.com/systemd/systemd/releases/tag/v253

There are a couple more lid options in NixOS:
https://search.nixos.org/options?channel=24.05&from=0&size=50&sort=relevance&type=packages&query=services.logind.lidSwitch

services.logind.lidSwitchExternalPower defaults to what is set for services.logind.lidSwitch so I’m fine with this setting.

services.logind.lidSwitchDocked defaults to ignore which I like as well since I’m often docked to an external monitor so this means I can put the lid down and it doesn’t Suspend.

@aaronshahriari
Copy link

Has anyone figured out how to get this to work without that awesome before an after script? Its so weird that setting a simple 'suspend-then-hibernate' still won't work on NixOS.

@jyap808
Copy link

jyap808 commented Nov 21, 2024

Has anyone figured out how to get this to work without that awesome before an after script? Its so weird that setting a simple 'suspend-then-hibernate' still won't work on NixOS.

Read my 2 replies above. Suspend-then-hibernate works.

@HuzaifaTP
Copy link

@jyap808 thanks a lot for the details :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment