#!/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 [] ( [])" 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"