90 lines
3.2 KiB
Django/Jinja
90 lines
3.2 KiB
Django/Jinja
#!/bin/bash
|
|
# Pending upgrades, held packages, reboot flag and age of the package lists as node exporter textfile
|
|
# metrics. apt-daily and apt-daily-upgrade run it after their update and upgrade (drop-ins).
|
|
set -euo pipefail
|
|
|
|
host={{ inventory_hostname }}
|
|
textfile_dir={{ apt_updates_textfile_dir }}
|
|
out=$textfile_dir/apt_updates.prom
|
|
tmp=$(mktemp "$textfile_dir/.apt_updates.XXXXXX")
|
|
trap 'rm -f "$tmp"' EXIT
|
|
|
|
# Architectures a pending package can have: the native and foreign ones, plus "all" for architecture-independent
|
|
# packages. Each gets a row per kind even at 0, so a resolved backlog reads as 0 and not as a vanished series.
|
|
archs="$(dpkg --print-architecture) $(dpkg --print-foreign-architectures | tr '\n' ' ') all"
|
|
|
|
# The simulated upgrade lists "Inst <package> [<installed>] (<candidate> <origins> [<arch>])" per package. The candidate
|
|
# is the newest version, which can come from regular updates while an older, still newer-than-installed version came
|
|
# from the security archive. So a package counts as security when any version above the installed one in its
|
|
# "apt-cache policy" table (newest first, installed marked with ***) has a security origin, everything else as regular.
|
|
sim=$(apt-get -s upgrade)
|
|
names=$(awk '/^Inst / { print $2 }' <<< "$sim")
|
|
security=""
|
|
if [[ -n $names ]]; then
|
|
# shellcheck disable=SC2086
|
|
security=$(apt-cache policy $names | awk '
|
|
/^[^ ]/ { pkg = $1; sub(/:$/, "", pkg); newer = 1; next }
|
|
/^ \*\*\* / { newer = 0; next }
|
|
/^ [0-9]+ / { if (newer && tolower($0) ~ /security/) print pkg }
|
|
')
|
|
fi
|
|
|
|
pending=$(SECURITY="$security" awk -v host="$host" -v archs="$archs" '
|
|
BEGIN {
|
|
n = split(ENVIRON["SECURITY"], s, "\n")
|
|
for (i = 1; i <= n; i++) sec[s[i]] = 1
|
|
}
|
|
/^Inst / {
|
|
line = $0
|
|
sub(/^[^(]*\(/, "", line)
|
|
sub(/\).*$/, "", line)
|
|
n = split(line, f, " ")
|
|
pkg_arch = f[n]
|
|
gsub(/\[|\]/, "", pkg_arch)
|
|
kind = ($2 in sec) ? "security" : "regular"
|
|
count[kind, pkg_arch]++
|
|
}
|
|
END {
|
|
split(archs, a, " ")
|
|
for (i in a) {
|
|
if (!(("security", a[i]) in count)) count["security", a[i]] = 0
|
|
if (!(("regular", a[i]) in count)) count["regular", a[i]] = 0
|
|
}
|
|
for (key in count) {
|
|
split(key, k, SUBSEP)
|
|
printf "apt_upgrades_pending{host=\"%s\",origin=\"%s\",arch=\"%s\"} %d\n", host, k[1], k[2], count[key]
|
|
}
|
|
}' <<< "$sim")
|
|
|
|
held=$(apt-mark showhold | wc -l)
|
|
|
|
reboot=0
|
|
if [[ -e /run/reboot-required ]]; then
|
|
reboot=1
|
|
fi
|
|
|
|
# Debian writes update-stamp, Ubuntu update-success-stamp; both are touched by a successful update. Both are missing
|
|
# until apt.systemd.daily has run an update, then the package cache stands in.
|
|
stamp=/var/lib/apt/periodic/update-success-stamp
|
|
if [[ ! -e $stamp ]]; then
|
|
stamp=/var/lib/apt/periodic/update-stamp
|
|
fi
|
|
if [[ ! -e $stamp ]]; then
|
|
stamp=/var/cache/apt/pkgcache.bin
|
|
fi
|
|
cache=$(stat -c %Y "$stamp")
|
|
|
|
{
|
|
echo '# TYPE apt_upgrades_pending gauge'
|
|
echo "$pending"
|
|
echo '# TYPE apt_upgrades_held gauge'
|
|
echo "apt_upgrades_held{host=\"$host\"} $held"
|
|
echo '# TYPE node_reboot_required gauge'
|
|
echo "node_reboot_required{host=\"$host\"} $reboot"
|
|
echo '# TYPE apt_package_cache_timestamp_seconds gauge'
|
|
echo "apt_package_cache_timestamp_seconds{host=\"$host\"} $cache"
|
|
} > "$tmp"
|
|
|
|
chmod 0644 "$tmp"
|
|
mv -f "$tmp" "$out"
|