I bought an old mSATA SSD off eBay and put it into the WWAN slot of my ThinkPad. Apparently it's slower than the other SSD, but maybe good enough to use as a scratch disk and for hibernation.

I created a swap partition of about 18 GB on the new SSD at /dev/sdb1 and added it and a new /scratch partition at /dev/sdb2. Get their UUIDs using blkid:

❯ sudo blkid /dev/sdb1
/dev/sdb1: UUID="059137db-89c2-408c-b6bb-4a8b3fe87fe2" TYPE="swap" PARTUUID="2728293e-caf2-4a32-ba96-5fdfaef3be65"

❯ sudo blkid /dev/sdb2
/dev/sdb2: UUID="b11918a3-0f6e-4105-a59c-e95364504817" BLOCK_SIZE="4096" TYPE="ext4" PARTUUID="30276e12-d20a-4b5d-8b7f-2fdf86e9d655"

Add these partitions to fstab using the UUIDs:

UUID=059137db-89c2-408c-b6bb-4a8b3fe87fe2 none swap sw 0 0
UUID=b11918a3-0f6e-4105-a59c-e95364504817 /scratch ext4 noatime,errors=remount-ro 0 0

Starting with instructions here and more instructions at Pop Planet Info:

Check that the kernel supports hibernation:

❯ cat /sys/power/state
freeze mem disk

disk means hibernation is supported.

Add a boot option with kernelstub:

sudo kernelstub -a “resume=UUID=059137db-89c2-408c-b6bb-4a8b3fe87fe2"

Create a /etc/initramfs-tools/conf.d/resume file pointing to the swap partition:

resume=UUID=059137db-89c2-408c-b6bb-4a8b3fe87fe2

Update the configuration:

sudo update-initramfs -u

Install pm-utils:

sudo apt install pm-utils

Create a file override.conf in /etc/systemd/system/systemd-hibernate.service.d (using pm-hibernate instead of s2disk like in the instructions, because uswsusp doesn`t seem to exist anymore):

[Service]
ExecStart=
ExecStartPre=-/usr/bin/run-parts -v -a pre /usr/lib/systemd/system-sleep
ExecStart=/usr/sbin/pm-hibernate
ExecStartPost=-/usr/bin/run-parts -v --reverse -a post /usr/lib/systemd/system-sleep

Now we can hibernate with:

systemctl hibernate

Suspend, then hibernate

To get suspend, then hibernate after a delay, follow these instructions, again modified because uswsusp no longer seems to exist.

Create this script as /usr/local/bin/s2smart, replacing s2disk with pm-hibernate:

#/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..."
    pm-hibernate
    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."

Add HibernateDelaySec=3600" to /etc/systemd/sleep.conf

Create a file override.conf in /etc/systemd/system/systemd-suspend-then-hibernate.service.d:

[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

Edit /etc/systemd/logind.conf to check that these lines are present and not commented:

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

Hibernating without a password

Use visudo to add these lines to /etc/sudoers:

# Allow members of group sudo to hibernate without a password
%sudo	ALL=NOPASSWD: /usr/bin/systemctl hibernate,/usr/bin/systemctl hybrid-sleep

Adding hibernate options to the power menu

Following these instructions:

Add these lines to /etc/polkit-1/localauthority/50-local.d/com.ubuntu.enable-hibernate.pkla:

[Re-enable hibernate by default in upower]
Identity=unix-user:*
Action=org.freedesktop.upower.hibernate
ResultActive=yes

[Re-enable hibernate by default in logind]
Identity=unix-user:*
Action=org.freedesktop.login1.hibernate;org.freedesktop.login1.handle-hibernate-key;org.freedesktop.login1;org.freedesktop.login1.hibernate-multiple-sessions;org.freedesktop.login1.hibernate-ignore-inhibit
ResultActive=yes

Then add this Gnome extension Hibernate Status Button.