123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- #!/usr/bin/env bash
- # This program will create a hibernation-ready swapfile in a btrfs subvolume and enable hibernation
- # Copyright Bryan C. Roessler 2021
- # This software is released under the Apache License.
- # https://www.apache.org/licenses/LICENSE-2.0
- # Tested on: Fedora 34
- VERBOSE=true
- function init() {
- SWAP_FILE=${SWAP_FILE:-"/.swap/swapfile"}
- [[ "$#" -lt 1 ]] && return 0
- if input=$(getopt -s v -l image-size:,swap-size:,swap-file:,device-uuid:,verbose -- "$@"); then
- eval set -- "$input"
- while true; do
- case "$1" in
- --image-size)
- shift && IMAGE_SIZE="$1"
- ;;
- --swap-size)
- shift && SWAP_SIZE="$1"
- ;;
- --swap-file)
- declare -x SWAP_FILE
- shift && SWAP_FILE="$1"
- ;;
- --device-uuid)
- shift && DEVICE_UUID="$1"
- ;;
- --verbose|v)
- VERBOSE=true
- ;;
- --)
- shift
- break
- ;;
- esac
- shift
- done
- else
- echo "Incorrect option provided"
- exit 1
- fi
- }
- function debug() {
- [[ "$VERBOSE" == true ]] && echo "$@"
- }
- function get_mem_size() {
- declare -ig MEM_SIZE MEM_SIZE_GB
- MEM_SIZE=$(free -b | grep "Mem:" | tr -s ' ' | cut -d" " -f2)
- MEM_SIZE_GB=$(numfmt --from=iec --to-unit=G "$MEM_SIZE")
- debug "MEM_SIZE_GB: ${MEM_SIZE_GB}"
- }
- #######################################
- # Get user y/N/iec response
- # Arguments:
- # 1. Text to display
- # 2. Global variable name to store response
- #######################################
- function response_iec() {
- declare _response _response_l _response_u
- read -r -p "$* [y/N/iec]: " _response
- _response_l="${_response,,}"
- _response_u="${_response^^}"
- [[ "$_response_l" =~ ^(yes|y) ]] && return 0
- if [[ "$_response" =~ ^([[:digit:]]+[[:alnum:]])$ || "$_response" =~ ^([[:digit:]]+)$ ]]; then
- if ! declare -gi "$2"="$(numfmt --from=iec "$_response_u")"; then
- echo "$_response is not a valid IEC value (ex. 42 512K 10M 7G 3.5T)"
- return 1
- else
- declare -gi "$2"_GB="$(numfmt --from=iec --to-unit=Gi "$_response_u")"
- return 0
- fi
- fi
- exit 1
- }
- function set_swap_size() {
- declare -gi SWAP_SIZE_GB
- declare _response _response_l
- # By default make the swapfile size the same as the system RAM
- # This is the safest approach at the expense of disk space
- # You can improve hibernation speed by tweaking $IMAGE_SIZE instead.
- [[ ! -v SWAP_SIZE ]] && SWAP_SIZE=$MEM_SIZE
- #SWAP_SIZE_MB=$(numfmt --from=iec --to-unit=Mi "$SWAP_SIZE")
- SWAP_SIZE_GB=$(numfmt --from=iec --to-unit=G "$SWAP_SIZE")
-
- # TODO incrementally use increasing IEC nominators
- echo "Ideal swapfile size is equal to system RAM: ${MEM_SIZE_GB}G"
-
- # TODO See above, also for image size
- until response_iec "Set swapfile size to ${SWAP_SIZE_GB}G?" "SWAP_SIZE"; do
- echo "Retrying..."
- done
- debug "SWAP_SIZE_GB: ${SWAP_SIZE_GB}G"
- }
- function set_image_size() {
- declare _response _response_l
- declare -i mem_size_target current_image_size current_image_size_gb
- # Set ideal image size
- if [[ ! -v IMAGE_SIZE ]]; then
- # Sensible default (greater of 1/4 swapfile or ram size)
- mem_size_target=$(( MEM_SIZE / 4 ))
- swap_size_target=$(( SWAP_SIZE / 4 ))
- IMAGE_SIZE=$(( mem_size_target > swap_size_target ? mem_size_target : swap_size_target ))
- IMAGE_SIZE_GB=$(numfmt --from=iec --to-unit=G "$IMAGE_SIZE")
- fi
- current_image_size=$(cat /sys/power/image_size)
- current_image_size_gb=$(numfmt --from=iec --to-unit=G "$current_image_size")
-
- echo "Ideal image target size is 1/4 of the RAM or swapfile size"
- echo "Swapfile size: ${SWAP_SIZE_GB}G"
- echo "RAM size: ${MEM_SIZE_GB}G"
- until response_iec "Resize /sys/power/image_size from ${current_image_size_gb}G to ${IMAGE_SIZE_GB}G?" "IMAGE_SIZE"; do
- echo "Retrying..."
- done
- debug "IMAGE_SIZE_GB: ${IMAGE_SIZE_GB}G"
- }
- function write_image_size() {
- echo "$IMAGE_SIZE_GB" | sudo tee /sys/power/image_size
- }
- function make_swapfile () {
- declare parent_dir
- parent_dir=$(dirname "$SWAP_FILE")
- ! [[ -d "$parent_dir" ]] && \
- if ! sudo btrfs sub create "$parent_dir"; then
- echo "Could not create a btrfs subvolume at $parent_dir"
- echo "Please check your permissions/filesystem"
- exit 1
- fi
- sudo touch "$SWAP_FILE"
- #sudo truncate -s 0 "$SWAP_FILE"
- sudo chattr +C "$SWAP_FILE"
- sudo fallocate --length "$SWAP_SIZE" "$SWAP_FILE"
- #sudo btrfs property set "$SWAP_FILE" compression none
- #sudo dd if=/dev/zero of="$SWAP_FILE" bs=1M count="$SWAP_SIZE_MB" status=progress
- sudo chmod 600 "$SWAP_FILE"
- sudo mkswap "$SWAP_FILE"
- #sudo swapon "$SWAP_FILE"
- }
- function add_to_dracut() {
- echo 'add_dracutmodules+=" resume "' | sudo tee /etc/dracut.conf.d/50-resume.conf
- sudo dracut -f
- }
- # function add_to_fstab() {
- # echo "Backing up /etc/fstab to /tmp/fstab.bk"
- # cp /etc/fstab /tmp/fstab.bk
- # echo "$SWAP_FILE none swap sw 0 0" | sudo tee -a /etc/fstab
- # }
- function get_offset() {
- physical_offset=$(./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9)
- debug "Physical offset: $physical_offset"
- }
- function get_device() {
- if ! [[ -v DEVICE_UUID ]]; then
- DEVICE_UUID=$(findmnt -no UUID -T "$SWAP_FILE")
- # if ! DEVICE_UUID=$(sudo blkid | grep /dev/mapper/luks | cut -d' ' -f3); then
- # echo "Guessing device UUID failed. Please specify a --device-uuid and rerun"
- # exit 1
- # fi
- fi
- if [[ ! $DEVICE_UUID == ^UUID=* ]]; then
- DEVICE_UUID="UUID=${DEVICE_UUID}"
- fi
- debug "Device UUID: $DEVICE_UUID"
- }
- function get_offset() {
- declare -i physical_offset
- [[ -v RESUME_OFFSET ]] && return 0
- RESUME_OFFSET=$(sudo filefrag -v "$SWAP_FILE" | head -n 4 | tail -n 1 | awk '{print $4}')
-
- # tempdir="$(mktemp --directory --tmpdir= enable-hibernate-XXXXXXXXX)"
- # pushd "$tempdir" || return 1
- # curl -O "https://raw.githubusercontent.com/osandov/osandov-linux/master/scripts/btrfs_map_physical.c"
- # gcc -O2 -o btrfs_map_physical btrfs_map_physical.c
- # page_size=$(getconf PAGESIZE)
- # # ./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9
- # sudo bash -c "export physical_offset=$(./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9);"
- # echo "$physical_offset"
- # popd || return 1
- # rm -rf "$tempdir"
- # RESUME_OFFSET=$(( physical_offset / page_size ))
- debug "Resume offset: $RESUME_OFFSET"
- }
- function update_grub() {
- echo "Backing up /etc/default/grub to /tmp/grub.bk"
- cp /etc/default/grub /tmp/grub.bk
- if grub_cmdline_linux=$(grep GRUB_CMDLINE_LINUX /etc/default/grub); then
- if [[ $grub_cmdline_linux == *resume=* ]]; then
- sudo sed -i "s/resume=.* /resume=${DEVICE_UUID} /" /etc/default/grub
- sudo sed -i "s/resume_offset=.* /resume_offset=${RESUME_OFFSET} /" /etc/default/grub
- else
- sudo sed -i "s/GRUB_CMDLINE_LINUX_DEFAULT.*/& resume=${DEVICE_UUID} resume_offset=${RESUME_OFFSET}/" /etc/default/grub
- fi
- else
- echo "Your /etc/default/grub cannot be edited automatically, please add: "
- echo "resume=${DEVICE_UUID} resume_offset=${RESUME_OFFSET}"
- echo "...to your kernel command line in /etc/default/grub,"
- echo 'and run "sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg"'
- exit 1
- fi
-
- sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg
- }
- function live_apply() {
- #declare devid
- #devid=$(lsblk | grep luks |cut -d' ' -f4)
- echo "${RESUME_OFFSET}" | sudo tee /sys/power/resume_offset
- }
- function main() {
- init "$@" && \
- get_mem_size && \
- set_swap_size && \
- set_image_size && \
- make_swapfile && \
- get_offset && \
- add_to_dracut && \
- update_grub && \
- suggest_reboot
- }
- main "$@"
- exit $?
|