422 lines
9.1 KiB
Bash
422 lines
9.1 KiB
Bash
#!/bin/bash
|
|
# PLONK - PLONK Leaves Only Needed Kernels
|
|
#
|
|
# This script removes old Debian kernels
|
|
# It also purges old kernel packages left in "rc" state
|
|
#
|
|
# In short: old kernels go plonk.
|
|
#
|
|
# Author: bisco <bisco__AT__younerd__DOT_org>
|
|
# Script inspired by the following cyberciti post:
|
|
# https://www.cyberciti.biz/faq/debian-ubuntu-linux-delete-old-kernel-images-command/
|
|
|
|
set -euo pipefail
|
|
|
|
### Variables
|
|
|
|
APT_GET="$(command -v apt-get || true)"
|
|
DPKG_QUERY="$(command -v dpkg-query || true)"
|
|
|
|
KERNELVER="$(uname -r)"
|
|
KEEP_KERNELS="2"
|
|
|
|
ACTION=""
|
|
ASSUME_YES="no"
|
|
|
|
KERNELS_TO_KEEP=()
|
|
ABIS_TO_KEEP=()
|
|
|
|
OLD_KERNELS=()
|
|
RC_KERNELS=()
|
|
PKGS_TO_PURGE=()
|
|
|
|
### End Variables
|
|
|
|
|
|
### Code.
|
|
### Please don't edit below unless if you know what you're doing.
|
|
|
|
|
|
### Check if something went wrong
|
|
check_error(){
|
|
echo "====> ERROR!"
|
|
echo "====> Script failed on line ${BASH_LINENO[0]}"
|
|
exit 1
|
|
}
|
|
|
|
|
|
### Run check_error function on errors
|
|
trap check_error ERR
|
|
|
|
|
|
### Show help and exits
|
|
usage(){
|
|
echo "PLONK - PLONK Leaves Only Needed Kernels"
|
|
echo ""
|
|
echo "Old kernels go plonk."
|
|
echo ""
|
|
echo "Usage: $0 [option]"
|
|
echo ""
|
|
echo "-l | --list : list old kernel packages"
|
|
echo "-n | --dry-run : simulate old kernel packages removal"
|
|
echo "-r | --remove : remove old kernel packages"
|
|
echo "-k | --keep NUM : number of kernels to keep. Default: ${KEEP_KERNELS}"
|
|
echo "-y | --yes : assume yes when removing packages"
|
|
echo "-h | --help : show this help and exits"
|
|
echo ""
|
|
echo "Examples:"
|
|
echo "$0 --list"
|
|
echo "$0 --dry-run"
|
|
echo "sudo $0 --remove"
|
|
echo "sudo $0 --remove --keep 3"
|
|
echo ""
|
|
}
|
|
|
|
|
|
### Check if all binaries are properly installed on target system
|
|
check_binary(){
|
|
if [ "${APT_GET}" == "" ] || [ "${DPKG_QUERY}" == "" ]; then
|
|
echo "[FAIL] Some binaries not found."
|
|
echo "[INFO] Please make sure you have: apt-get, dpkg-query"
|
|
exit 1
|
|
else
|
|
echo "[OK] All binaries are properly installed. Let's go on!"
|
|
fi
|
|
}
|
|
|
|
|
|
### Check if the script has been run by root when removing packages
|
|
check_root(){
|
|
if [ "${ACTION}" != "remove" ]; then
|
|
return
|
|
fi
|
|
|
|
if [ "$(id -u)" == "0" ]; then
|
|
echo "[OK] Check root permissions"
|
|
else
|
|
echo "[FAIL] Check root permissions"
|
|
echo "[INFO] Run this script as root or sudo"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
|
|
### Extract kernel version from package name
|
|
pkg_to_kernel_version(){
|
|
local PKG="${1%%:*}"
|
|
|
|
case "${PKG}" in
|
|
linux-image-unsigned-*)
|
|
echo "${PKG#linux-image-unsigned-}"
|
|
;;
|
|
linux-image-*)
|
|
echo "${PKG#linux-image-}"
|
|
;;
|
|
linux-headers-*)
|
|
echo "${PKG#linux-headers-}"
|
|
;;
|
|
*)
|
|
return 1
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
### Extract Debian kernel ABI from kernel package version
|
|
kernel_abi_from_version(){
|
|
local VERSION="$1"
|
|
|
|
case "${VERSION}" in
|
|
*-cloud-amd64)
|
|
echo "${VERSION%-cloud-amd64}"
|
|
;;
|
|
*-rt-amd64)
|
|
echo "${VERSION%-rt-amd64}"
|
|
;;
|
|
*-amd64)
|
|
echo "${VERSION%-amd64}"
|
|
;;
|
|
*-686-pae)
|
|
echo "${VERSION%-686-pae}"
|
|
;;
|
|
*-686)
|
|
echo "${VERSION%-686}"
|
|
;;
|
|
*-cloud-arm64)
|
|
echo "${VERSION%-cloud-arm64}"
|
|
;;
|
|
*-rt-arm64)
|
|
echo "${VERSION%-rt-arm64}"
|
|
;;
|
|
*-arm64)
|
|
echo "${VERSION%-arm64}"
|
|
;;
|
|
*-common)
|
|
echo "${VERSION%-common}"
|
|
;;
|
|
*)
|
|
echo "${VERSION%-*}"
|
|
;;
|
|
esac
|
|
}
|
|
|
|
|
|
### List installed Debian kernel packages
|
|
list_installed_kernel_packages(){
|
|
{
|
|
"${DPKG_QUERY}" -W -f='${db:Status-Abbrev}\t${binary:Package}\n' \
|
|
'linux-image-[0-9]*' \
|
|
'linux-image-unsigned-[0-9]*' \
|
|
'linux-headers-[0-9]*' \
|
|
2>/dev/null || true
|
|
} | awk '$1 == "ii" { print $2 }' | sort -u
|
|
}
|
|
|
|
|
|
### List residual Debian kernel packages in rc state
|
|
list_rc_kernel_packages(){
|
|
{
|
|
"${DPKG_QUERY}" -W -f='${db:Status-Abbrev}\t${binary:Package}\n' \
|
|
'linux-image-[0-9]*' \
|
|
'linux-image-unsigned-[0-9]*' \
|
|
'linux-headers-[0-9]*' \
|
|
2>/dev/null || true
|
|
} | awk '$1 == "rc" { print $2 }' | sort -u
|
|
}
|
|
|
|
|
|
### List installed Debian kernel image versions
|
|
list_installed_kernel_versions(){
|
|
{
|
|
"${DPKG_QUERY}" -W -f='${db:Status-Abbrev}\t${binary:Package}\n' \
|
|
'linux-image-[0-9]*' \
|
|
'linux-image-unsigned-[0-9]*' \
|
|
2>/dev/null || true
|
|
} | awk '$1 == "ii" { print $2 }' \
|
|
| while read -r PKG; do
|
|
pkg_to_kernel_version "${PKG}"
|
|
done \
|
|
| sort -V -u
|
|
}
|
|
|
|
|
|
### Build the list of kernels that should be kept
|
|
build_keep_list(){
|
|
local INSTALLED_KERNELS=()
|
|
local VERSION=""
|
|
local ABI=""
|
|
local I=""
|
|
|
|
mapfile -t INSTALLED_KERNELS < <(list_installed_kernel_versions)
|
|
|
|
KERNELS_TO_KEEP=("${KERNELVER}")
|
|
|
|
for (( I=${#INSTALLED_KERNELS[@]}-1; I>=0 && ${#KERNELS_TO_KEEP[@]}<KEEP_KERNELS; I-- )); do
|
|
VERSION="${INSTALLED_KERNELS[$I]}"
|
|
|
|
if [ "${VERSION}" == "${KERNELVER}" ]; then
|
|
continue
|
|
fi
|
|
|
|
KERNELS_TO_KEEP+=("${VERSION}")
|
|
done
|
|
|
|
ABIS_TO_KEEP=()
|
|
|
|
for VERSION in "${KERNELS_TO_KEEP[@]}"; do
|
|
ABI="$(kernel_abi_from_version "${VERSION}")"
|
|
ABIS_TO_KEEP+=("${ABI}")
|
|
done
|
|
}
|
|
|
|
|
|
### Check if a package belongs to a kernel that should be kept
|
|
is_kernel_to_keep(){
|
|
local PKG="$1"
|
|
local VERSION=""
|
|
local ABI=""
|
|
local KEEP=""
|
|
|
|
VERSION="$(pkg_to_kernel_version "${PKG}")"
|
|
ABI="$(kernel_abi_from_version "${VERSION}")"
|
|
|
|
for KEEP in "${KERNELS_TO_KEEP[@]}"; do
|
|
if [ "${VERSION}" == "${KEEP}" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
for KEEP in "${ABIS_TO_KEEP[@]}"; do
|
|
if [ "${ABI}" == "${KEEP}" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
return 1
|
|
}
|
|
|
|
|
|
### List old installed kernel packages
|
|
list_old_installed_kernels(){
|
|
local PKG=""
|
|
|
|
while read -r PKG; do
|
|
if [ "${PKG}" == "" ]; then
|
|
continue
|
|
fi
|
|
|
|
if ! is_kernel_to_keep "${PKG}"; then
|
|
echo "${PKG}"
|
|
fi
|
|
done < <(list_installed_kernel_packages) | sort -u
|
|
}
|
|
|
|
|
|
### Build final package list
|
|
build_purge_list(){
|
|
mapfile -t OLD_KERNELS < <(list_old_installed_kernels)
|
|
mapfile -t RC_KERNELS < <(list_rc_kernel_packages)
|
|
|
|
PKGS_TO_PURGE=("${OLD_KERNELS[@]}" "${RC_KERNELS[@]}")
|
|
}
|
|
|
|
|
|
### Print script summary
|
|
print_summary(){
|
|
echo ""
|
|
echo "Running kernel : ${KERNELVER}"
|
|
echo "Keeping kernels: ${KERNELS_TO_KEEP[*]}"
|
|
echo "Keeping ABI : ${ABIS_TO_KEEP[*]}"
|
|
echo ""
|
|
|
|
if [ "${#OLD_KERNELS[@]}" -gt 0 ]; then
|
|
echo "Old installed kernel packages:"
|
|
printf ' %s\n' "${OLD_KERNELS[@]}"
|
|
else
|
|
echo "[OK] No old installed kernel packages found."
|
|
fi
|
|
|
|
echo ""
|
|
|
|
if [ "${#RC_KERNELS[@]}" -gt 0 ]; then
|
|
echo "Residual rc kernel packages:"
|
|
printf ' %s\n' "${RC_KERNELS[@]}"
|
|
else
|
|
echo "[OK] No residual rc kernel packages found."
|
|
fi
|
|
|
|
echo ""
|
|
}
|
|
|
|
|
|
### Simulate package purge
|
|
dry_run(){
|
|
if [ "${#PKGS_TO_PURGE[@]}" -eq 0 ]; then
|
|
echo "[OK] Nothing to purge."
|
|
exit 0
|
|
fi
|
|
|
|
"${APT_GET}" -s purge "${PKGS_TO_PURGE[@]}"
|
|
}
|
|
|
|
|
|
### Remove old kernel packages
|
|
remove_kernels(){
|
|
local ANSWER=""
|
|
|
|
if [ "${#PKGS_TO_PURGE[@]}" -eq 0 ]; then
|
|
echo "[OK] Nothing to purge."
|
|
exit 0
|
|
fi
|
|
|
|
if [ "${ASSUME_YES}" != "yes" ]; then
|
|
read -r -p "Proceed with apt-get purge? [y/N] " ANSWER
|
|
|
|
case "${ANSWER}" in
|
|
y|Y|yes|YES)
|
|
;;
|
|
*)
|
|
echo "Aborted."
|
|
exit 0
|
|
;;
|
|
esac
|
|
fi
|
|
|
|
if [ "${ASSUME_YES}" == "yes" ]; then
|
|
"${APT_GET}" -y purge "${PKGS_TO_PURGE[@]}"
|
|
else
|
|
"${APT_GET}" purge "${PKGS_TO_PURGE[@]}"
|
|
fi
|
|
|
|
echo ""
|
|
echo "Old kernels have been successfully plonked."
|
|
}
|
|
|
|
|
|
### Parse options
|
|
while [ "$#" -gt 0 ]; do
|
|
case "$1" in
|
|
-l | --list)
|
|
ACTION="list"
|
|
;;
|
|
-n | --dry-run)
|
|
ACTION="dry-run"
|
|
;;
|
|
-r | --remove)
|
|
ACTION="remove"
|
|
;;
|
|
-k | --keep)
|
|
shift
|
|
|
|
if ! [[ "${1:-}" =~ ^[0-9]+$ ]]; then
|
|
echo "[FAIL] --keep requires a number"
|
|
exit 1
|
|
fi
|
|
|
|
if [ "$1" -lt 1 ]; then
|
|
echo "[FAIL] --keep requires a number greater than zero"
|
|
exit 1
|
|
fi
|
|
|
|
KEEP_KERNELS="$1"
|
|
;;
|
|
-y | --yes)
|
|
ASSUME_YES="yes"
|
|
;;
|
|
-h | --help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
usage
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
shift
|
|
done
|
|
|
|
|
|
### Main
|
|
if [ "${ACTION}" == "" ]; then
|
|
usage
|
|
exit 1
|
|
fi
|
|
|
|
check_binary
|
|
check_root
|
|
build_keep_list
|
|
build_purge_list
|
|
print_summary
|
|
|
|
case "${ACTION}" in
|
|
list)
|
|
exit 0
|
|
;;
|
|
dry-run)
|
|
dry_run
|
|
;;
|
|
remove)
|
|
remove_kernels
|
|
;;
|
|
esac |