[
  {
    "path": ".gitignore",
    "content": "zram-swap/\nsrc/\npkg/\n*.pkg.tar.*\n*.log.*\n*.log\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2019 Scott B\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# zram-swap\nA simple zram swap script for modern systemd Linux\n\nhttps://github.com/foundObjects/zram-swap\n\n### Why?\n\nI wrote zram-swap because I couldn't find a simple modern replacement for the Ubuntu\n`zram-config` package that included basic error handling, didn't make device sizing\nmistakes and kept user-facing configuration straightforward and easy to understand.\n\n### Installation and Usage\n\n*Arch Linux:*\n\nInstall from the AUR: `paru -S zram-swap-git`\n\nor directly from GitHub:\n\n```sh\nmkdir zram-swap-git && cd zram-swap-git\nwget https://raw.githubusercontent.com/foundObjects/zram-swap/arch-packaging/PKGBUILD\nmakepkg -Cci\n```\n\nYou'll need to start and enable `zram-swap.service` after installation on Arch.\nMake any configuration changes to `/etc/default/zram-swap` first if desired, see below for details.\n\n*Others:*\n\n```sh\ngit clone https://github.com/foundObjects/zram-swap.git\ncd zram-swap && sudo ./install.sh\n```\n\nThe install script starts the zram-swap.service automatically after installation\nand enables the systemd service during boot. The default allocation creates an lz4\nzram device that should use around half of physical memory when completely full.\n\nI chose lz4 as the default to give low spec machines (systems that often see\nthe greatest benefit from swap on zram) every performance edge I could.\nWhile lzo-rle is quite fast on modern high-performance hardware a machine like a\nRaspberry Pi or a low spec laptop appreciates every speed advantage I can give it.\n\n### Configuration\n\nEdit `/etc/default/zram-swap` if you'd like to change the compression algorithm or\nswap allocation and then restart zram-swap with `systemctl restart zram-swap.service`.\nThe configuration file is heavily commented and self-documenting.\n\nA very simple configuration that's expected to use roughly 2GB RAM might look\nsomething like:\n\n```sh\n# override fractional calculations and specify a fixed swap size\n_zram_fixedsize=\"6G\"\n\n# compression algorithm to employ (lzo, lz4, zstd, lzo-rle)\n_zram_algorithm=\"lzo-rle\"\n```\n\nRemember that the ZRAM device size references uncompressed data, real memory\nutilization should be ~2-3x smaller than the zram device size due to compression.\n\n#### A quick note RE: compression algorithms:\n\nThe default configuration using lz4 should work well for most people. lzo may\nprovide slightly better RAM utilization at a cost of slightly more expensive\ndecompression. zstd should provide better compression than lz\\* and still be\nmoderately fast on most machines. On very modern kernels and reasonably fast\nhardware the most balanced choice is probably lzo-rle. On low spec machines\n(ARM SBCs, ARM laptops, thin clients, etc) you'll probably want to stick with\nlz4.\n\n### Debugging\n\nTo view a script debug trace either start zram-swap.sh with `zram-swap.sh -x (start|stop)`\nor uncomment the debug flag in `/etc/default/zram-swap`:\n\n```sh\n# setting _zram_swap_debugging to any non-zero value enables debugging\n# default: undefined\n_zram_swap_debugging=\"beep boop\"\n```\n\n### Compatibility\n\nTested on Linux 4.4 through Linux 5.14.\n\nRequirements are minimal; Underneath the systemd service wrapper the swap setup\nscript needs only a posix shell, `modprobe`, `zramctl` and very basic `awk` and\n`grep` support to function. It should work in pretty much any modern Linux\nenvironment.\n"
  },
  {
    "path": "install.sh",
    "content": "#!/bin/sh\n# source: https://github.com/foundObjects/zram-swap\n# shellcheck disable=SC2039,SC2162\n\n#[ \"$(id -u)\" -eq '0' ] || { echo \"This script requires root.\" && exit 1; }\ncase \"$(readlink /proc/$$/exe)\" in */bash) set -euo pipefail ;; *) set -eu ;; esac\n\n# ensure a predictable environment\nexport PATH=/usr/sbin:/usr/bin:/sbin:/bin\n\\unalias -a\n\n# installer main body:\n_main() {\n  # ensure $1 exists so 'set -u' doesn't error out\n  { [ \"$#\" -eq \"0\" ] && set -- \"\"; } > /dev/null 2>&1\n\n  case \"$1\" in\n    \"--uninstall\")\n      # uninstall, requires root\n      assert_root\n      _uninstall\n      ;;\n    \"--install\" | \"\")\n      # install dpkg hooks, requires root\n      assert_root\n      _install \"$@\"\n      ;;\n    *)\n      # unknown flags, print usage and exit\n      _usage\n      ;;\n  esac\n  exit 0\n}\n\n_install() {\n  configdiff=''\n  newconfig=''\n  if systemctl -q is-active zram-swap.service; then\n    echo \"Stopping zram-swap service\"\n    systemctl stop zram-swap.service\n  fi\n\n  echo \"Installing script and service ...\"\n  install -o root zram-swap.sh /usr/local/sbin/zram-swap.sh\n  install -o root -m 0644 service/zram-swap.service /etc/systemd/system/zram-swap.service\n\n  # rename & cleanup old version config file\n  if [ -f /etc/default/zram-swap-service ]; then\n    mv -f /etc/default/zram-swap-service /etc/default/zram-swap\n    chown root:root /etc/default/zram-swap\n    chmod 0644 /etc/default/zram-swap\n  fi\n\n  if [ -f /etc/default/zram-swap ]; then\n    {\n      set +e\n      configdiff=$(diff -y /etc/default/zram-swap service/zram-swap.config)\n      set -e\n    } > /dev/null 2>&1\n    if [ -n \"$configdiff\" ]; then\n      yn=''\n      echo \"Local configuration differs from packaged version\"\n      echo\n      echo \"Install package default configuration? Local config will be saved as /etc/default/zram-swap.oldconfig\"\n      while true; do\n        echo \"(I)nstall package default / (K)eep local configuration / View (D)iff\"\n        printf \"[i/k/d]: \"\n        read yn\n        case \"$yn\" in\n          [Ii]*)\n            echo \"Installing package default ...\"\n            install -o root -m 0644 --backup --suffix=\".oldconfig\" service/zram-swap.config /etc/default/zram-swap\n            newconfig='y'\n            break\n            ;;\n          [Kk]*) break ;;\n          [Dd]*) printf \"%s\\n\\n\" \"$configdiff\" ;;\n        esac\n      done\n    fi\n  else\n    install -o root -m 0644 -b service/zram-swap.config /etc/default/zram-swap\n  fi\n\n  echo \"Reloading systemd unit files and enabling boot-time service ...\"\n  systemctl daemon-reload\n  systemctl enable zram-swap.service\n\n  if [ -n \"$newconfig\" ]; then\n    cat <<- HEREDOC\n\t\tConfiguration file updated; old config saved as /etc/default/zram-swap.oldconfig\n\n\t\tPlease review changes between configurations and then start the service with\n\t\tsystemctl start zram-swap.service\n\t\tHEREDOC\n  else\n    echo \"Starting zram-swap service ...\"\n    systemctl start zram-swap.service\n  fi\n\n  echo\n  echo \"zram-swap service installed successfully!\"\n  echo\n}\n\n_uninstall() {\n  if systemctl -q is-active zram-swap.service; then\n    echo \"Stopping zram-swap service\"\n    systemctl stop zram-swap.service\n  fi\n\n  echo \"Uninstalling script and systemd service.\"\n  if [ -f /etc/systemd/system/zram-swap.service ]; then\n    systemctl disable zram-swap.service || true\n    rm -f /etc/systemd/system/zram-swap.service\n  fi\n  if [ -f /usr/local/sbin/zram-swap.sh ]; then\n    rm -f /usr/local/sbin/zram-swap.sh\n  fi\n  echo \"Reloading systemd unit files\"\n  systemctl daemon-reload\n\n  echo \"zram-swap service uninstalled; remove configuration /etc/default/zram-swap if desired\"\n}\n\nassert_root() { [ \"$(id -u)\" -eq '0' ] || { echo \"This action requires root.\" && exit 1; }; }\n_usage() { echo \"Usage: $(basename \"$0\") (--install|--uninstall)\"; }\n\n_main \"$@\"\n"
  },
  {
    "path": "service/zram-swap.config",
    "content": "# compression algorithm to employ (lzo, lz4, zstd, lzo-rle)\n# default: lz4\n_zram_algorithm=\"lz4\"\n\n# portion of system ram to use as zram swap (expression: \"1/2\", \"2/3\", \"0.5\", etc)\n# default: \"1/2\"\n_zram_fraction=\"1/2\"\n\n# setting _zram_swap_debugging to any non-zero value enables debugging\n# default: undefined\n#_zram_swap_debugging=\"beep boop\"\n\n# expected compression factor; set this by hand if your compression results are\n# drastically different from the estimates below\n#\n# Note: These are the defaults coded into /usr/local/sbin/zram-swap.sh; don't alter\n#       these values, use the override variable '_comp_factor' below.\n#\n# defaults if otherwise unset:\n#\tlzo*|zstd)  _comp_factor=\"3\"   ;; # expect 3:1 compression from lzo*, zstd\n#\tlz4)        _comp_factor=\"2.5\" ;; # expect 2.5:1 compression from lz4\n#\t*)          _comp_factor=\"2\"   ;; # default to 2:1 for everything else\n#\n#_comp_factor=\"2.5\"\n\n# if set skip device size calculation and create a fixed-size swap device\n# (size, in MiB/GiB, eg: \"250M\" \"500M\" \"1.5G\" \"2G\" \"6G\" etc.)\n#\n# Note: this is the swap device size before compression, real memory use will\n#       depend on compression results, a 2-3x reduction is typical\n#\n#_zram_fixedsize=\"2G\"\n\n# vim:ft=sh:ts=2:sts=2:sw=2:et:\n"
  },
  {
    "path": "service/zram-swap.service",
    "content": "[Unit]\nDescription=zram swap service\nRequires=systemd-modules-load.service\nBefore=shutdown.target\n#After=\n\n[Service]\nType=oneshot\nExecStart=/usr/local/sbin/zram-swap.sh start\nExecStop=/usr/local/sbin/zram-swap.sh stop\nRemainAfterExit=true\n\n[Install]\nWantedBy=sysinit.target\n#RequiredBy=\n\n# vim:ft=systemd\n"
  },
  {
    "path": "zram-swap.sh",
    "content": "#!/bin/sh\n# source: https://github.com/foundObjects/zram-swap\n# shellcheck disable=SC2013,SC2039,SC2064\n\n[ \"$(id -u)\" -eq '0' ] || { echo \"This script requires root.\" && exit 1; }\ncase \"$(readlink /proc/$$/exe)\" in */bash) set -euo pipefail ;; *) set -eu ;; esac\n\n# ensure a predictable environment\nexport PATH=/usr/sbin:/usr/bin:/sbin:/bin\n\\unalias -a\n\n# parse debug flag early so we can trace user configuration\n[ \"$#\" -gt \"0\" ] && [ \"$1\" = \"-x\" ] && shift && set -x\n\n# set sane defaults, see /etc/default/zram-swap for explanations\n_zram_fraction=\"1/2\"\n_zram_algorithm=\"lz4\"\n_comp_factor=''\n_zram_fixedsize=''\n_zram_swap_debug=''\n\n# load user config\n[ -f /etc/default/zram-swap ] &&\n  . /etc/default/zram-swap\n\n# support a debugging flag in the config file so people don't have to edit the systemd service\n# to enable debugging\n[ -n \"$_zram_swap_debug\" ] && set -x\n\n# set expected compression ratio based on algorithm -- we'll use this to\n# calculate how much uncompressed swap data we expect to fit into our\n# target ram allocation.  skip if already set in user config\nif [ -z \"$_comp_factor\" ]; then\n  case $_zram_algorithm in\n    lzo* | zstd) _comp_factor=\"3\" ;;\n    lz4) _comp_factor=\"2.5\" ;;\n    *) _comp_factor=\"2\" ;;\n  esac\nfi\n\n# main script:\n_main() {\n  if ! modprobe zram; then\n    err \"main: Failed to load zram module, exiting\"\n    return 1\n  fi\n\n  # make sure `set -u` doesn't cause 'case \"$1\"' to throw errors below\n  { [ \"$#\" -eq \"0\" ] && set -- \"\"; } > /dev/null 2>&1\n\n  case \"$1\" in\n    \"init\" | \"start\")\n      if grep -q zram /proc/swaps; then\n        err \"main: zram swap already in use, exiting\"\n        return 1\n      fi\n      _init\n      ;;\n    \"end\" | \"stop\")\n      if ! grep -q zram /proc/swaps; then\n        err \"main: no zram swaps to cleanup, exiting\"\n        return 1\n      fi\n      _end\n      ;;\n    \"restart\")\n      # TODO: stub for restart support\n      echo \"not supported yet\"\n      _usage\n      exit 1\n      ;;\n    *)\n      _usage\n      exit 1\n      ;;\n  esac\n}\n\n# initialize swap\n_init() {\n  if [ -n \"$_zram_fixedsize\" ]; then\n    if ! _regex_match \"$_zram_fixedsize\" '^[[:digit:]]+(\\.[[:digit:]]+)?(G|M)$'; then\n      err \"init: Invalid size '$_zram_fixedsize'. Format sizes like: 100M 250M 1.5G 2G etc.\"\n      exit 1\n    fi\n    # Use user supplied zram size\n    mem=\"$_zram_fixedsize\"\n  else\n    # Calculate memory to use for zram\n    totalmem=$(awk '/MemTotal/{print $2}' /proc/meminfo)\n    mem=$(calc \"$totalmem * $_comp_factor * $_zram_fraction * 1024\")\n  fi\n\n  # NOTE: zramctl sometimes fails if we don't wait for the module to settle after loading\n  #       we'll retry a couple of times with slightly increasing delays before giving up\n  _device=''\n  for i in $(seq 3); do\n    # sleep for \"0.1 * $i\" seconds rounded to 2 digits\n    sleep \"$(calc 2 \"0.1 * $i\")\"\n    _device=$(zramctl -f -s \"$mem\" -a \"$_zram_algorithm\") || true\n    [ -b \"$_device\" ] && break\n  done\n\n  if [ -b \"$_device\" ]; then\n    # cleanup the device if swap setup fails\n    trap \"_rem_zdev $_device\" EXIT\n    mkswap \"$_device\"\n    swapon -d -p 15 \"$_device\"\n    trap - EXIT\n    return 0\n  else\n    err \"init: Failed to initialize zram device\"\n    return 1\n  fi\n}\n\n# end swapping and cleanup\n_end() {\n  ret=\"0\"\n  for dev in $(awk '/zram/ {print $1}' /proc/swaps); do\n    swapoff \"$dev\"\n    if ! _rem_zdev \"$dev\"; then\n      err \"end: Failed to remove zram device $dev\"\n      ret=1\n    fi\n  done\n  return \"$ret\"\n}\n\n# Remove zram device with retry\n_rem_zdev() {\n  if [ ! -b \"$1\" ]; then\n    err \"rem_zdev: No zram device '$1' to remove\"\n    return 1\n  fi\n  for i in $(seq 3); do\n    # sleep for \"0.1 * $i\" seconds rounded to 2 digits\n    sleep \"$(calc 2 \"0.1 * $i\")\"\n    zramctl -r \"$1\" || true\n    [ -b \"$1\" ] || break\n  done\n  if [ -b \"$1\" ]; then\n    err \"rem_zdev: Couldn't remove zram device '$1' after 3 attempts\"\n    return 1\n  fi\n  return 0\n}\n\n# posix substitute for bash pattern matching [[ $foo =~ bar-pattern ]]\n# usage: _regex_match \"$foo\" \"bar-pattern\"\n_regex_match() { echo \"$1\" | grep -Eq -- \"$2\" > /dev/null 2>&1; }\n\n# calculate with variable precision\n# usage: calc (int; precision := 0) (str; expr to evaluate)\ncalc() {\n  _regex_match \"$1\" '^[[:digit:]]+$' && { n=\"$1\" && shift; } || n=0\n  LC_NUMERIC=C awk \"BEGIN{printf \\\"%.${n}f\\\", $*}\"\n}\n\nerr() { echo \"Err $*\" >&2; }\n_usage() { echo \"Usage: $(basename \"$0\") (start|stop)\"; }\n\n_main \"$@\"\n"
  }
]