#!/bin/sh # do some basic sanity checks # shellcheck source=conf/default.conf . "${0%/*}/../conf/default.conf" # TODO: read information from database usage() { >&2 echo '' >&2 echo 'sanity-check [options] [checks]: check sanity of build master' >&2 echo '' >&2 echo 'possible options:' >&2 echo ' -h|--help: Show this help and exit.' >&2 echo ' -q|--quiet: Only print errors found.' >&2 echo ' -r|--really-quiet: Do not print anything.' >&2 echo ' -w|--webserver: Generate output suitable for webserver.' [ -z "$1" ] && exit 1 || exit "$1" } i_am_insane() { if [ ! -s "${work_dir}/build-master-sanity" ]; then printf '\001ACTION goes insane.\001\n' | \ sponge "${irc_dir}/#archlinux-ports/in" fi echo 'build master is insane' > \ "${work_dir}/build-master-sanity" echo 'SANITY CHECK FAILED' >> \ "${tmp_dir}/messages" exit 1 } eval set -- "$( getopt -o hqrw \ --long help \ --long quiet \ --long really-quiet \ --long webserver \ -n "$(basename "$0")" -- "$@" || \ echo usage )" silence=0 repos="${standalone_package_repositories} ${stable_package_repositories} ${testing_package_repositories} ${staging_package_repositories}" web=false while true do case "$1" in -h|--help) usage 0 ;; -q|--quiet) silence=1 ;; -r|--really-quiet) silence=2 ;; -w|--webserver) web=true ;; --) shift break ;; *) >&2 echo 'Whoops, forgot to implement option "'"$1"'" internally.' exit 42 ;; esac shift done exec 9> "${sanity_check_lock_file}" if ! flock -n 9; then >&2 echo 'Sanity check skipped, cannot acquire lock.' exit fi finish() { if ${web}; then { printf '%s\n' \ '' \ '' \ 'result of archlinux32 build master'"'"'s sanity check' \ '' \ '' printf '%s
\n' "$(date)" sed 's|$|
|' "${tmp_dir}/messages" printf '%s\n' \ '' \ '' } > \ "${webserver_directory}/master-sanity.html" else cat "${tmp_dir}/messages" >&2 fi rm -rf --one-file-system "${tmp_dir}" } tmp_dir=$(mktemp -d 'tmp.sanity-check.XXXXXXXXXX' --tmpdir) touch "${tmp_dir}/messages" trap 'finish' EXIT if [ $# -eq 0 ]; then set -- git-repositories build-list mysql repos package-database state-files fi while [ $# -gt 0 ]; do case "$1" in git-repositories) [ ${silence} -gt 0 ] || \ printf 'checking git repositories ...' >> \ "${tmp_dir}/messages" for repo in ${repo_names}; do eval 'repo_path="${repo_paths__'"${repo}"'}"' repo_revision=$( cat "${work_dir}/${repo}.revision" ) if ! git -C "${repo_path}" archive "${repo_revision}" -- | \ tar -t > /dev/null; then if [ ${silence} -le 1 ]; then printf '\nThe repository %s (%s) cannot archive the current revision %s.\n' \ "${repo}" "${repo_path}" "${repo_revision}" >> \ "${tmp_dir}/messages" fi i_am_insane fi done [ ${silence} -gt 0 ] || \ echo ' passed.' >> \ "${tmp_dir}/messages" ;; build-list) [ ${silence} -gt 0 ] || \ printf 'checking build-list ...' >> \ "${tmp_dir}/messages" errors=$( grep -vn '^\S\+ [0-9a-f]\{40\} [0-9a-f]\{40\} \S\+$' "${work_dir}/build-list" ) || true if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following build orders are wrongly formatted:\n%s\n' \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi errors=$( cut -d' ' -f1 < \ "${work_dir}/build-list" | \ sort | \ uniq -d ) if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following packages have duplicate build orders:\n%s\n' \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi errors=$( { cut -d' ' -f1 < \ "${work_dir}/build-list" cat "${work_dir}/deletion-list" } | \ sort | \ uniq -d ) if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following packages appear on the build- and deletion-list:\n%s\n' \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi [ ${silence} -gt 0 ] || \ echo ' passed.' >> \ "${tmp_dir}/messages" ;; repos) [ ${silence} -gt 0 ] || \ printf 'checking repos on master mirror ...' >> \ "${tmp_dir}/messages" errors=$( { # shellcheck disable=SC2086 printf 'expected %s\n' ${repos} ls_master_mirror 'i686' | \ sed 's|^|found |' } | \ sort -k2 | \ uniq -uf1 ) if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following repos are missing or obsolete on the mirror:\n%s\n' \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi [ ${silence} -gt 0 ] || \ echo ' passed.' >> \ "${tmp_dir}/messages" ;; package-database) for repo in ${repos}; do [ ${silence} -gt 0 ] || \ printf 'checking consistency of repository "%s" on the master mirror ...' "${repo}" >> \ "${tmp_dir}/messages" packages=$( ls_master_mirror "i686/${repo}" | \ grep '\.pkg\.tar\.xz\(\.sig\)\?$' ) || true errors=$( echo "${packages}" | \ grep '\S' | \ sed ' s|^\(.*\.pkg\.tar\.xz\)$|package \1| s|^\(.*\.pkg\.tar\.xz\)\.sig$|signature \1| ' | \ sort -k2 | \ uniq -cf1 | \ grep -v '^\s*2\s' | \ awk '{print $2 " " $3}' ) || true if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following packages in %s are missing a signature or vice versa:\n%s\n' \ "${repo}" \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi ${master_mirror_rsync_command} \ "${master_mirror_rsync_directory}/i686/${repo}/${repo}.db.tar.gz" \ "${master_mirror_rsync_directory}/i686/${repo}/${repo}.files.tar.gz" \ "${tmp_dir}/" errors=$( { tar -Oxzf "${tmp_dir}/${repo}.db.tar.gz" --wildcards '*/desc' 2>/dev/null | \ sed -n ' /^%FILENAME%$/ { N s/^.*\n/in_database / p } ' echo "${packages}" | \ sed ' /\.pkg\.tar\.xz$/ !d s/^/in_repository / ' | \ sort -u } | \ sort -k2 | \ uniq -uf1 ) if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following packages in %s are missing from the database or vice versa:\n%s\n' \ "${repo}" \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi errors=$( { tar -tzf "${tmp_dir}/${repo}.files.tar.gz" | \ grep '/$' | \ sed ' s|/$|| s|^|in_database | ' echo "${packages}" | \ grep '\S' | \ sed ' s|-[^-]\+$|| s|^|in_repository | ' | \ sort -u } | \ sort -k2 | \ uniq -uf1 ) if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following packages in %s are missing from the file-database or vice versa:\n%s\n' \ "${repo}" \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi find "${tmp_dir:?}" -mindepth 1 \( -not -name 'messages' \) -delete [ ${silence} -gt 0 ] || \ echo ' passed.' >> \ "${tmp_dir}/messages" done ;; state-files) for status in 'staging' 'testing'; do [ ${silence} -gt 0 ] || \ printf 'checking state-files of "%s" ...' "${status}" >> \ "${tmp_dir}/messages" errors=$( { if [ "${status}" = 'staging' ]; then find "${work_dir}/package-states" -name '*.done' \ -exec sed 's|^|package-state-file |' '{}' \; else find "${work_dir}/package-states" \( -name '*.testing' -o -name '*.tested' \) \ -exec sed 's|^|package-state-file |' '{}' \; fi ls_master_mirror 'i686' | \ grep "${status}\$" | \ while read -r repo; do ls_master_mirror "i686/${repo}" done | \ grep '\.pkg\.tar\.xz$' | \ sed 's|^|package-file |' } | \ sort -k2 | \ uniq -cf1 | \ grep -v '^\s*2\s' | \ awk '{print $2 " " $3}' ) if [ -n "${errors}" ]; then if [ ${silence} -le 1 ]; then printf '\nThe following %s packages do not have state files or vice versa:\n%s\n' \ "${status}" \ "${errors}" >> \ "${tmp_dir}/messages" fi i_am_insane fi [ ${silence} -gt 0 ] || \ echo ' passed.' >> \ "${tmp_dir}/messages" done ;; mysql) [ ${silence} -gt 0 ] || \ printf 'checking mysql-sanity-check-file ...' >> \ "${tmp_dir}/messages" if [ -s "${webserver_directory}/mysql-sanity.html" ]; then if [ ${silence} -le 1 ]; then printf '\nThere is something wrong with the database:\n' cat "${webserver_directory}/mysql-sanity.html" fi >> \ "${tmp_dir}/messages" i_am_insane fi # hopefully, this gets rid of false positives :-) sleep 1 if find "${work_dir}" -mindepth 1 -maxdepth 1 -name 'tmp.mysql-functions.query.*' | \ grep '\S' >> \ "${tmp_dir}/messages"; then i_am_insane fi [ ${silence} -gt 0 ] || \ echo ' passed.' >> \ "${tmp_dir}/messages" ;; *) [ ${silence} -gt 1 ] || \ >&2 printf 'unknown sanity-check "%s".\n' "$1" exit 2 ;; esac shift done if [ -f "${work_dir}/build-master-sanity" ]; then rm "${work_dir}/build-master-sanity" printf '\001ACTION resumes sanity.\001\n' | \ sponge "${irc_dir}/#archlinux-ports/in" fi