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.
-
-
Save mattdenner/befcf099f5cfcc06ea04dcdd4969a221 to your computer and use it in GitHub Desktop.
{ 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"; | |
}; | |
} |
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.
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";
};
}
Very nice! Note for the 13'' AMD Framework laptop: AC
needs to be ACAD
:)
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";
@jyap808 thats great news ! thanks for the update, will try it out :)
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.
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.
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.
@jyap808 thanks a lot for the details :)
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".