fedora-enable-hibernate 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. #!/usr/bin/env bash
  2. # This program will create a hibernation-ready swapfile in a btrfs subvolume and enable hibernation
  3. # Copyright Bryan C. Roessler 2021
  4. # This software is released under the Apache License.
  5. # https://www.apache.org/licenses/LICENSE-2.0
  6. # Tested on: Fedora 34
  7. VERBOSE=true
  8. function init() {
  9. SWAP_FILE=${SWAP_FILE:-"/.swap/swapfile"}
  10. [[ "$#" -lt 1 ]] && return 0
  11. if input=$(getopt -s v -l image-size:,swap-size:,swap-file:,device-uuid:,verbose -- "$@"); then
  12. eval set -- "$input"
  13. while true; do
  14. case "$1" in
  15. --image-size)
  16. shift && IMAGE_SIZE="$1"
  17. ;;
  18. --swap-size)
  19. shift && SWAP_SIZE="$1"
  20. ;;
  21. --swap-file)
  22. declare -x SWAP_FILE
  23. shift && SWAP_FILE="$1"
  24. ;;
  25. --device-uuid)
  26. shift && DEVICE_UUID="$1"
  27. ;;
  28. --verbose|v)
  29. VERBOSE=true
  30. ;;
  31. --)
  32. shift
  33. break
  34. ;;
  35. esac
  36. shift
  37. done
  38. else
  39. echo "Incorrect option provided"
  40. exit 1
  41. fi
  42. }
  43. function debug() {
  44. [[ "$VERBOSE" == true ]] && echo "$@"
  45. }
  46. function get_mem_size() {
  47. declare -ig MEM_SIZE MEM_SIZE_GB
  48. MEM_SIZE=$(free -b | grep "Mem:" | tr -s ' ' | cut -d" " -f2)
  49. MEM_SIZE_GB=$(numfmt --from=iec --to-unit=G "$MEM_SIZE")
  50. debug "MEM_SIZE_GB: ${MEM_SIZE_GB}"
  51. }
  52. #######################################
  53. # Get user y/N/iec response
  54. # Arguments:
  55. # 1. Text to display
  56. # 2. Global variable name to store response
  57. #######################################
  58. function response_iec() {
  59. declare _response _response_l _response_u
  60. read -r -p "$* [y/N/iec]: " _response
  61. _response_l="${_response,,}"
  62. _response_u="${_response^^}"
  63. [[ "$_response_l" =~ ^(yes|y) ]] && return 0
  64. if [[ "$_response" =~ ^([[:digit:]]+[[:alnum:]])$ || "$_response" =~ ^([[:digit:]]+)$ ]]; then
  65. if ! declare -gi "$2"="$(numfmt --from=iec "$_response_u")"; then
  66. echo "$_response is not a valid IEC value (ex. 42 512K 10M 7G 3.5T)"
  67. return 1
  68. else
  69. declare -gi "$2"_GB="$(numfmt --from=iec --to-unit=Gi "$_response_u")"
  70. return 0
  71. fi
  72. fi
  73. exit 1
  74. }
  75. function set_swap_size() {
  76. declare -gi SWAP_SIZE_GB
  77. declare _response _response_l
  78. # By default make the swapfile size the same as the system RAM
  79. # This is the safest approach at the expense of disk space
  80. # You can improve hibernation speed by tweaking $IMAGE_SIZE instead.
  81. [[ ! -v SWAP_SIZE ]] && SWAP_SIZE=$MEM_SIZE
  82. #SWAP_SIZE_MB=$(numfmt --from=iec --to-unit=Mi "$SWAP_SIZE")
  83. SWAP_SIZE_GB=$(numfmt --from=iec --to-unit=G "$SWAP_SIZE")
  84. # TODO incrementally use increasing IEC nominators
  85. echo "Ideal swapfile size is equal to system RAM: ${MEM_SIZE_GB}G"
  86. # TODO See above, also for image size
  87. until response_iec "Set swapfile size to ${SWAP_SIZE_GB}G?" "SWAP_SIZE"; do
  88. echo "Retrying..."
  89. done
  90. debug "SWAP_SIZE_GB: ${SWAP_SIZE_GB}G"
  91. }
  92. function set_image_size() {
  93. declare _response _response_l
  94. declare -i mem_size_target current_image_size current_image_size_gb
  95. # Set ideal image size
  96. if [[ ! -v IMAGE_SIZE ]]; then
  97. # Sensible default (greater of 1/4 swapfile or ram size)
  98. mem_size_target=$(( MEM_SIZE / 4 ))
  99. swap_size_target=$(( SWAP_SIZE / 4 ))
  100. IMAGE_SIZE=$(( mem_size_target > swap_size_target ? mem_size_target : swap_size_target ))
  101. IMAGE_SIZE_GB=$(numfmt --from=iec --to-unit=G "$IMAGE_SIZE")
  102. fi
  103. current_image_size=$(cat /sys/power/image_size)
  104. current_image_size_gb=$(numfmt --from=iec --to-unit=G "$current_image_size")
  105. echo "Ideal image target size is 1/4 of the RAM or swapfile size"
  106. echo "Swapfile size: ${SWAP_SIZE_GB}G"
  107. echo "RAM size: ${MEM_SIZE_GB}G"
  108. until response_iec "Resize /sys/power/image_size from ${current_image_size_gb}G to ${IMAGE_SIZE_GB}G?" "IMAGE_SIZE"; do
  109. echo "Retrying..."
  110. done
  111. debug "IMAGE_SIZE_GB: ${IMAGE_SIZE_GB}G"
  112. }
  113. function write_image_size() {
  114. echo "$IMAGE_SIZE_GB" | sudo tee /sys/power/image_size
  115. }
  116. function make_swapfile () {
  117. declare parent_dir
  118. parent_dir=$(dirname "$SWAP_FILE")
  119. ! [[ -d "$parent_dir" ]] && \
  120. if ! sudo btrfs sub create "$parent_dir"; then
  121. echo "Could not create a btrfs subvolume at $parent_dir"
  122. echo "Please check your permissions/filesystem"
  123. exit 1
  124. fi
  125. sudo touch "$SWAP_FILE"
  126. #sudo truncate -s 0 "$SWAP_FILE"
  127. sudo chattr +C "$SWAP_FILE"
  128. sudo fallocate --length "$SWAP_SIZE" "$SWAP_FILE"
  129. #sudo btrfs property set "$SWAP_FILE" compression none
  130. #sudo dd if=/dev/zero of="$SWAP_FILE" bs=1M count="$SWAP_SIZE_MB" status=progress
  131. sudo chmod 600 "$SWAP_FILE"
  132. sudo mkswap "$SWAP_FILE"
  133. #sudo swapon "$SWAP_FILE"
  134. }
  135. function add_to_dracut() {
  136. echo 'add_dracutmodules+=" resume "' | sudo tee /etc/dracut.conf.d/50-resume.conf
  137. sudo dracut -f
  138. }
  139. # function add_to_fstab() {
  140. # echo "Backing up /etc/fstab to /tmp/fstab.bk"
  141. # cp /etc/fstab /tmp/fstab.bk
  142. # echo "$SWAP_FILE none swap sw 0 0" | sudo tee -a /etc/fstab
  143. # }
  144. function get_offset() {
  145. physical_offset=$(./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9)
  146. debug "Physical offset: $physical_offset"
  147. }
  148. function get_device() {
  149. if ! [[ -v DEVICE_UUID ]]; then
  150. DEVICE_UUID=$(findmnt -no UUID -T "$SWAP_FILE")
  151. # if ! DEVICE_UUID=$(sudo blkid | grep /dev/mapper/luks | cut -d' ' -f3); then
  152. # echo "Guessing device UUID failed. Please specify a --device-uuid and rerun"
  153. # exit 1
  154. # fi
  155. fi
  156. if [[ ! $DEVICE_UUID == ^UUID=* ]]; then
  157. DEVICE_UUID="UUID=${DEVICE_UUID}"
  158. fi
  159. debug "Device UUID: $DEVICE_UUID"
  160. }
  161. function get_offset() {
  162. declare -i physical_offset
  163. [[ -v RESUME_OFFSET ]] && return 0
  164. RESUME_OFFSET=$(sudo filefrag -v "$SWAP_FILE" | head -n 4 | tail -n 1 | awk '{print $4}')
  165. # tempdir="$(mktemp --directory --tmpdir= enable-hibernate-XXXXXXXXX)"
  166. # pushd "$tempdir" || return 1
  167. # curl -O "https://raw.githubusercontent.com/osandov/osandov-linux/master/scripts/btrfs_map_physical.c"
  168. # gcc -O2 -o btrfs_map_physical btrfs_map_physical.c
  169. # page_size=$(getconf PAGESIZE)
  170. # # ./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9
  171. # sudo bash -c "export physical_offset=$(./btrfs_map_physical "$SWAP_FILE" | head -n2 | tail -n1 | cut -f9);"
  172. # echo "$physical_offset"
  173. # popd || return 1
  174. # rm -rf "$tempdir"
  175. # RESUME_OFFSET=$(( physical_offset / page_size ))
  176. debug "Resume offset: $RESUME_OFFSET"
  177. }
  178. function update_grub() {
  179. echo "Backing up /etc/default/grub to /tmp/grub.bk"
  180. cp /etc/default/grub /tmp/grub.bk
  181. if grub_cmdline_linux=$(grep GRUB_CMDLINE_LINUX /etc/default/grub); then
  182. if [[ $grub_cmdline_linux == *resume=* ]]; then
  183. sudo sed -i "s/resume=.* /resume=${DEVICE_UUID} /" /etc/default/grub
  184. sudo sed -i "s/resume_offset=.* /resume_offset=${RESUME_OFFSET} /" /etc/default/grub
  185. else
  186. sudo sed -i "s/GRUB_CMDLINE_LINUX_DEFAULT.*/& resume=${DEVICE_UUID} resume_offset=${RESUME_OFFSET}/" /etc/default/grub
  187. fi
  188. else
  189. echo "Your /etc/default/grub cannot be edited automatically, please add: "
  190. echo "resume=${DEVICE_UUID} resume_offset=${RESUME_OFFSET}"
  191. echo "...to your kernel command line in /etc/default/grub,"
  192. echo 'and run "sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg"'
  193. exit 1
  194. fi
  195. sudo grub2-mkconfig -o /boot/efi/EFI/fedora/grub.cfg
  196. }
  197. function live_apply() {
  198. #declare devid
  199. #devid=$(lsblk | grep luks |cut -d' ' -f4)
  200. echo "${RESUME_OFFSET}" | sudo tee /sys/power/resume_offset
  201. }
  202. function main() {
  203. init "$@" && \
  204. get_mem_size && \
  205. set_swap_size && \
  206. set_image_size && \
  207. make_swapfile && \
  208. get_offset && \
  209. add_to_dracut && \
  210. update_grub && \
  211. suggest_reboot
  212. }
  213. main "$@"
  214. exit $?