aboutsummarylogtreecommitdiffstats
path: root/kernel-rotate.sh
blob: 5d984c1a5186f8eca75cf9c5887fd5a3449f3b36 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
#!/bin/bash

all=false
kernels=()
mykver=$(uname -r)
myactive="$(<"/usr/lib/modules/${mykver}/pkgbase")"

# Read the optional config file for automation
[[ -f /etc/kernel-rotate.conf ]] && source /etc/kernel-rotate.conf

# Define defaults and insure sanity with lowercasing forced.
BOOT_PATH="${BOOT_PATH:-/boot}"
BACKUP_PATH="${BACKUP_PATH:-${BOOT_PATH}}"
ROTATION_STYLE="${ROTATION_STYLE:-none}"
ROTATION_STYLE="${ROTATION_STYLE,,}"
ROTATION_LIST="${ROTATION_LIST:-linux}"
ROTATION_LIST="${ROTATION_LIST,,}"


# Code begins
while read -r line; do  # build a list of kernels being updated this transaction
    $all && continue # Dump the remaining input buffer
    if [[ "${line}" != linux* ]]; then
        echo "Updating all kernels."
        all=true
        continue
    fi
    if [[ "${line}" =~ ^linux-.*-headers$ ]]; then
        # Skip linux header packages.
        continue
    fi

    #echo "Adding ${line} to kernel rotation..."
    kernels+=("${line}")
done

if $all; then  # apparently dracut/mkinitcpio is being updated, so let's just loop all
    kernels=()
    for line in /usr/lib/modules/*; do
        if pacman -Qqo "${line}/pkgbase" &> /dev/null; then
            kernels+=("$(<"${line}/pkgbase")")
        fi
    done
fi


for kernel in "${kernels[@]}"; do
    freeSpace="$(df "${BOOT_PATH}" | awk '{print $4}' | tail -1)"
    sizeUsed="$(du -sc "${BOOT_PATH}/vmlinuz-${kernel}" "${BOOT_PATH}/initramfs-${kernel}.img" "${BOOT_PATH}/initramfs-${kernel}-fallback.img" | grep total | cut -f1)"

    if [[ "${ROTATION_STYLE}" == "active" ]]; then
        # Check if current item is for the active running kernel
        if [[ "$myactive" != "$kernel" ]]; then
            echo "KERNEL ROTATION: Not rotating ${kernel}, not active kernel"
            continue
        fi
    elif [[ "${ROTATION_STYLE}" == "list" ]]; then
        # Check if current item is in the list of kernels to rotate
        found=false
        for i in ${ROTATION_LIST}; do
            if [[ "$i" == "$kernel" ]]; then
                found=true
                break
            fi
        done

        if ! $found; then
            echo "KERNEL ROTATION: Not rotating $kernel, not in the list"
            continue
        fi
    elif [[ "${ROTATION_STYLE}" == "none" ]]; then
        echo "KERNEL ROTATION: Not rotating ${kernel}, rotation disabled"
        continue
    fi

    if [[ "$BOOT_PATH" != "$BACKUP_PATH" ]]; then
        echo "KERNEL ROTATION: Rotating ${kernel} to ${BACKUP_PATH}"
    else
        echo "KERNEL ROTATION: Rotating ${kernel} to ${kernel}.old"
    fi

    USE_OLD=""
    [[ "$BOOT_PATH" == "$BACKUP_PATH" ]] && USE_OLD=".old"

    if (( sizeUsed > freeSpace )); then
        # Check if existing kernel backups would free up enough space to be rotated anyway
        freeable="$(du -sc "${BACKUP_PATH}/vmlinuz-${kernel}${USE_OLD}" "${BACKUP_PATH}/initramfs-${kernel}.img${USE_OLD}" "${BACKUP_PATH}/initramfs-${kernel}-fallback.img${USE_OLD}" | grep total | cut -f1)"

        if (( sizeUsed > (freeSpace + freeable) )); then
            echo "KERNEL ROTATION: WARNING: Not enough free space on /boot to perform kernel rotation"
            continue
        fi
    fi

    [[ "$BOOT_PATH" != "$BACKUP_PATH" ]] && mkdir -p "${BACKUP_PATH}"
    cp --reflink=auto "${BOOT_PATH}/vmlinuz-${kernel}" "${BACKUP_PATH}/vmlinuz-${kernel}${USE_OLD}"
    cp --reflink=auto "${BOOT_PATH}/initramfs-${kernel}.img" "${BACKUP_PATH}/initramfs-${kernel}.img${USE_OLD}"
    if [[ -f "${BOOT_PATH}/initramfs-${kernel}-fallback.img" ]]; then
        cp --reflink=auto "${BOOT_PATH}/initramfs-${kernel}-fallback.img" "${BACKUP_PATH}/initramfs-${kernel}-fallback.img${USE_OLD}"
    fi
done