#!/bin/bash # Repository: https://gitea.evolix.org/evolix/maj.sh/ # Exit codes : # - 30 : $skip_releases or $skip_packages is set to "all" # - 40 : current release is in $skip_releases list # - 50 : all upgradable packages are in the $skip_packages list # - 60 : current release is not in the $r_releases list # - 70 : at least an upgradable package is not in the $r_packages list VERSION="23.03.3" show_version() { cat <, Gregory Colpart , Romain Dessort , Ludovic Poujol , Jérémy Lecour and others. listupgrade.sh comes with ABSOLUTELY NO WARRANTY. This is free software, and you are welcome to redistribute it under certain conditions. See the GNU General Public Licence for details. END } # Parse line in retrieved upgrade file and ensure there is no malicious values. get_value() { file="$1" variable="$2" value="$(grep "^${variable}:" "${file}" | head -n 1 | cut -d ':' -f 2 | sed 's/^ //')" if echo "${value}" | grep -q -E '^[-.: [:alnum:]]*$'; then echo "${value}" else printf >&2 "Error parsing value \"%s\" for variable %s.\n" "${value}" "${variable}" fi } # Fetch which packages/releases will be upgraded. fetch_upgrade_info() { upgradeInfo=$(mktemp --tmpdir=/tmp listupgrade.XXX) wget --no-check-certificate --quiet --output-document="${upgradeInfo}" https://upgrades.evolix.org/upgrade # shellcheck disable=SC2181 if [ "$?" != "0" ]; then printf >&2 "Error fetching upgrade directives.\n" fi r_releases="$(get_value "${upgradeInfo}" "releases")" r_skip_releases="$(get_value "${upgradeInfo}" "skip_releases")" r_packages="$(get_value "${upgradeInfo}" "packages")" r_skip_packages="$(get_value "${upgradeInfo}" "skip_packages")" rm "${upgradeInfo}" } # Check if element $element is in (space separated) list $list. is_in() { list="$1" element="$2" for i in ${list}; do if [ "${element}" = "${i}" ]; then return 0 fi done return 1 } render_mail_template() { local template_file=$1 cat <"${template_file}" Content-Type: text/plain; charset="utf-8" Reply-To: equipe@evolix.fr From: equipe@evolix.net To: ${clientmail} Subject: Prochain creneau pour mise a jour de votre serveur ${hostname} X-Debian-Release: ${local_release} X-Packages: ${packagesParsable} X-Date: ${date} X-Listupgrade-Version: ${VERSION} Bonjour, Des mises-à-jour de sécurité ou mineures sont à réaliser sur votre serveur ${hostname}. Sauf indication contraire de votre part, le prochain créneau prévu pour intervenir manuellement pour réaliser ces mises-à-jour est : ${date} Si nous intervenons, un redémarrage des éventuels services concernés sera réalisé, entraînant a priori quelques secondes de coupure. Si nous ne sommes pas intervenus sur ce créneau, vous recevrez une nouvelle notification la semaine prochaine. Voici la listes de packages qui seront mis à jour : $(sort -h "${packages}" | uniq) Liste des packages dont la mise-à-jour a été manuellement suspendue : $(sort -h "${packagesHold}" | uniq) Liste des services qui seront redémarrés : $(sort -h "${servicesToRestart}" | uniq) N'hésitez pas à nous faire toute remarque sur ce créneau d'intervention le plus tôt possible. Cordialement, -- Équipe Evolix - Hébergement et Infogérance Open Source http://evolix.com | Twitter: @Evolix @EvolixNOC | http://blog.evolix.com EOT } # Files found in the directory passed as 1st argument # are executed if they are executable # and if their name doesn't contain a dot exec_hooks_in_dir() { hooks=$(find "${1}" -type f -executable -not -name '*.* -print0 | sort --zero-terminated --dictionary-order | xargs --no-run-if-empty --null --max-args=1') for hook in ${hooks}; do if ! cron_mode; then printf "Running '%s\`\n" "${hook}" fi ${hook} done } pre_hooks() { if [ -d "${hooksDir}/pre" ]; then exec_hooks_in_dir "${hooksDir}/pre" fi } post_hooks_and_exit() { status=${1:-0} if [ -d "${hooksDir}/post" ]; then exec_hooks_in_dir "${hooksDir}/post" fi exit ${status} } cron_mode() { test "${cron_mode}" = "1" } force_mode() { test "${force_mode}" = "1" } main() { if ! cron_mode; then echo "Updating lists..." fi # Update APT cache and get packages to upgrade and packages on hold. aptUpdateOutput=$(apt -o Dir::State::Lists="${listupgrade_state_dir}" update 2>&1 | (grep -E -ve '^(Listing|WARNING|$)' -e upgraded -e 'up to date' || true)) if echo "${aptUpdateOutput}" | grep -E "^Err(:[0-9]+)? http"; then echo "FATAL - Not able to fetch all sources (probably a pesky (mini)firewall). Please, fix me" >&2 post_hooks_and_exit 100 fi apt-mark showhold | sed -e 's/\(.\+\)/^\1\//' >"${packagesHold}" apt -o Dir::State::Lists="${listupgrade_state_dir}" list --upgradable 2>&1 | grep -v -f "${packagesHold}" | grep -v -E '^(Listing|WARNING|$)' >"${packages}" packagesParsable=$(cut -f 1 -d / <"${packages}" | tr '\n' ' ') # No updates? Exit! if [ ! -s "${packages}" ]; then if ! cron_mode; then echo "There is nothing to upgrade. Bye." >&2 fi post_hooks_and_exit 0 fi if [ ! -s "${packagesHold}" ]; then echo 'Aucun' >"${packagesHold}" fi local_release=$(cut -f 1 -d . >"${servicesToRestart}" elif echo "${pkg}" | grep -q "^nginx"; then echo "Nginx" >>"${servicesToRestart}" elif echo "${pkg}" | grep -q "^php5-fpm"; then echo "PHP FPM" >>"${servicesToRestart}" elif echo "${pkg}" | grep -q "^mysql-server"; then echo "MySQL" >>"${servicesToRestart}" elif echo "${pkg}" | grep -q "^mariadb-server"; then echo "MariaDB" >>"${servicesToRestart}" elif echo "${pkg}" | grep -qE "^postgresql-[[:digit:]]+(\.[[:digit:]]+)?$"; then echo "PostgreSQL" >>"${servicesToRestart}" elif echo "${pkg}" | grep -qE "^tomcat[[:digit:]]+$"; then echo "Tomcat" >>"${servicesToRestart}" elif [ "${pkg}" = "redis-server" ]; then echo "Redis" >>"${servicesToRestart}" elif [ "${pkg}" = "mongodb-server" ]; then echo "MondoDB" >>"${servicesToRestart}" elif echo "${pkg}" | grep -qE "^courier-(pop|imap)"; then echo "Courier POP/IMAP" >>"${servicesToRestart}" elif echo "${pkg}" | grep -qE "^dovecot-(pop|imap)d"; then echo "Dovecot POP/IMAP" >>"${servicesToRestart}" elif [ "${pkg}" = "samba" ]; then echo "Samba" >>"${servicesToRestart}" elif [ "${pkg}" = "slapd" ]; then echo "OpenLDAP" >>"${servicesToRestart}" elif [ "${pkg}" = "bind9" ]; then echo "Bind9" >>"${servicesToRestart}" elif [ "${pkg}" = "postfix" ]; then echo "Postfix" >>"${servicesToRestart}" elif [ "${pkg}" = "haproxy" ]; then echo "HAProxy" >>"${servicesToRestart}" elif [ "${pkg}" = "varnish" ]; then echo "Varnish" >>"${servicesToRestart}" elif [ "${pkg}" = "squid" ]; then echo "Squid" >>"${servicesToRestart}" elif [ "${pkg}" = "elasticsearch" ]; then echo "Elasticsearch" >>"${servicesToRestart}" elif [ "${pkg}" = "logstash" ]; then echo "Logstash" >>"${servicesToRestart}" elif [ "${pkg}" = "kibana" ]; then echo "Kibana" >>"${servicesToRestart}" elif [ "${pkg}" = "libc6" ]; then echo "Tous les services sont susceptibles d'être redémarrés (mise à jour de libc6)." >"${servicesToRestart}" break elif [ "${pkg}" = "libstdc++6" ]; then echo "Tous les services sont susceptibles d'être redémarrés (mise à jour de libstdc++6)." >"${servicesToRestart}" break elif echo "${pkg}" | grep -q "^libssl"; then echo "Tous les services sont susceptibles d'être redémarrés (mise à jour de libssl)." >"${servicesToRestart}" break fi done test ! -s "${servicesToRestart}" && echo "Aucun" >"${servicesToRestart}" render_mail_template "${template}" /usr/sbin/sendmail "${mailto}" <"${template}" if ! cron_mode; then echo "Dowloading packages..." fi # Now we try to fetch all the packages for the next update session downloadstatus=$(apt -o Dir::State::Lists="${listupgrade_state_dir}" dist-upgrade --assume-yes --download-only -q2 2>&1) echo "${downloadstatus}" | grep -q 'Download complete and in download only mode' # shellcheck disable=SC2181 if [ $? -ne 0 ]; then echo "${downloadstatus}" fi # Also, we try to update each container apt sources if which lxc-ls >/dev/null; then for container in $(lxc-ls); do aptUpdateOutput=$(lxc-attach -n "${container}" -- apt -o Dir::State::Lists="${listupgrade_state_dir}" update 2>&1 | (grep -Eve '^(Listing|WARNING|$)' -e upgraded -e 'up to date' || true)) if (echo "${aptUpdateOutput}" | grep -E "^Err(:[0-9]+)? http"); then echo "FATAL CONTAINER - Not able to fetch all sources (probably a pesky (mini)firewall). Please, fix me" >&2 post_hooks_and_exit 150 fi # Now we try to fetch all the packages for the next update session downloadstatus=$(lxc-attach -n "${container}" -- apt -o Dir::State::Lists="${listupgrade_state_dir}" dist-upgrade --assume-yes --download-only -q2 2>&1) if echo "${downloadstatus}" | grep -q 'Download complete and in download only mode'; then echo "${downloadstatus}" fi done fi } # Options parsing. while :; do case ${1} in -V | --version) show_version exit 0 ;; --cron) cron_mode=1 ;; -f | --force) # Ignore exclusions from "upgrade info" and do as if all releases and packages are to be upgraded force_mode=1 ;; -?* | [[:alnum:]]*) # ignore unknown options printf 'ERROR: Unknown option : %s\n' "$1" >&2 exit 1 ;; *) # Default case: If no more options then break out of the loop. break ;; esac shift done ## Do not stop on error. Instead we should catch them manually # set -e ## Error on unassigned variables set -u export LC_ALL=C configFile="/etc/evolinux/listupgrade.cnf" cron_mode=${cron_mode:-0} force_mode=${force_mode:-0} clientmail=$(grep EVOMAINTMAIL /etc/evomaintenance.cf | cut -d'=' -f2) mailto="${clientmail}" date="Ce jeudi entre 18h00 et 23h00." hostname=$(grep HOSTNAME /etc/evomaintenance.cf | cut -d'=' -f2) hostname=${hostname%%.evolix.net} listupgrade_state_dir="${listupgrade_state_dir:-/var/lib/listupgrade}" hooksDir="/etc/evolinux/listupgrade-hooks" # If hostname is composed with -, remove the first part. if [[ "${hostname}" =~ "-" ]]; then hostname=$(echo "${hostname}" | cut -d'-' -f2-) fi # Edit $configFile to override some variables. # shellcheck disable=SC1090,SC1091 [ -r "${configFile}" ] && . "${configFile}" # Create temporary files packages=$(mktemp --tmpdir=/tmp listupgrade.XXX) packagesHold=$(mktemp --tmpdir=/tmp listupgrade.XXX) servicesToRestart=$(mktemp --tmpdir=/tmp listupgrade.XXX) template=$(mktemp --tmpdir=/tmp listupgrade.XXX) # Remove temporary files on exit. # shellcheck disable=SC2064 trap "rm ${packages} ${packagesHold} ${servicesToRestart} ${template}" EXIT if ! cron_mode; then echo "À quelle date/heure allez vous planifier les mises à jour ?" echo "Exemple : le jeudi 6 mars entre 18h00 et 23h00" echo -n "> " read -r date echo "À qui envoyer le mail ?" echo -n "> " read -r mailto fi # Execute pre hooks pre_hooks # call main function main # Execute post hooks and exit post_hooks_and_exit 0