I have been using Pop!_OS 19.04 on my Dell XPS ‘13 recently and one of the missing features I wanted back was “suspend then hibernate”. systemd supports suspend-then-hibernate but as of now, it only supports native hibernation which is not working on my machine. After a lot of experimentation I have settled on a custom script and configuration to get it working.

If you’re interested in getting it working on your machine, follow these steps to enable it:

  1. I assume uswsusp hibernation is already installed and configured on the target machine.

  2. Place the provided script somewhere on your file system (I saved it as /usr/local/bin/s2smart):

    #!/bin/bash
       
    echo2 () {
        printf "$1\n"
        echo "$1" > /dev/kmsg
    }
       
    if [[ $EUID -ne 0 ]]; then
        echo "This script must be run as root" 
        exit 1
    fi
       
    hibernate_delay=`cat /etc/systemd/sleep.conf | grep -oP "HibernateDelaySec=\K(\d+)" 2> /dev/null || echo 3600`
    battery_capacity=`cat /sys/class/power_supply/BAT0/capacity 2> /dev/null || echo 0`
       
    if [ $battery_capacity -le 20 ]; then
        hibernate_delay=$(($hibernate_delay/2))
    elif [ $battery_capacity -le 50 ]; then
        hibernate_delay=$(($hibernate_delay/4))
    fi
       
    echo2 "Battery capacity at $battery_capacity%%. Will hibernate after $hibernate_delay seconds."
       
    # Record current time.
    curtime=$(date +%s)
    lock=/tmp/rtchibernate.lock
    echo "$curtime" > $lock
       
    # Entering sleep.
    echo2 "Entering suspend at $curtime. Will resume in $hibernate_delay seconds."
    rtcwake -m no -s $hibernate_delay
    s2ram
       
    # Exited the sleep.
    curtime=$(date +%s)
    sustime=`cat $lock`
    rm $lock
       
    echo2 "Exited suspend mode at $curtime."
       
    # Did we wake up due to the rtc timer above?
    if [ $(($curtime - $sustime)) -ge $hibernate_delay ]; then
        # Sometimes when you have too many cores that are too fast run-parts do mess things up.
        # lets get some sleep ;)
        sleep 2
        # Hibernate (suspend to disk)...
        echo2 "Entering hibernation mode at $curtime..."
        s2disk
        echo2 "Exited hibernation."
    else
        # Cancel the rtc timer and wake up normally.
        rtcwake -m disable
        echo2 "Cancelled RTC resume prematurely."
    fi
       
    echo2 "Suspend or hibernate finished. Welcome back."
    
  3. Add the line HibernateDelaySec=3600 into /etc/systemd/sleep.conf and adjust it to whatever your desired delay is.

  4. Create a file override.conf in /etc/systemd/system/systemd-suspend-then-hibernate.service.d (create the directory if needed):

    [Service]
    ExecStart=
    ExecStartPre=-/usr/bin/run-parts -v -a pre /usr/lib/systemd/system-sleep
    ExecStart=/usr/local/bin/s2smart
    ExecStartPost=-/usr/bin/run-parts -v --reverse -a post /usr/lib/systemd/system-sleep
    
  5. Finally, you will need to enable suspend-then-hibernate on lid close by modifying the /etc/systemd/logind.conf. Make sure the following lines are present and not commented out:

    HandleLidSwitch=suspend-then-hibernate
    HandleLidSwitchExternalPower=suspend-then-hibernate
    

There, hope this helps someone. I did not document all the other steps I took to enable hibernation since those can be found online with ease.