#!/bin/bash # mp-check | MatterLinux package check script # MatterLinux 2023-2024 (https://matterlinux.xyz) # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # You should have received a copy of the GNU General Public License # along with this program. If not, see . ############################# ## import common functions ## ############################# location=$(dirname "${0}") location=$(realpath "${location}") commonsh="$(echo "${location}" | sed 's/\/bin/\/lib/g')/mtsc-common.sh" source "${commonsh}" > /dev/null if [ "${?}" != "0" ]; then echo "Failed to import mtsc-common" exit 1 fi ################# ## global vars ## ################# warnc=0 tmpdir="/tmp/.mp-clean" required_files=( "DATA" "HASHES" "CHANGES" "files.tar.gz" ) required_keys=( "version" "desc" "size" ) root_dirs=( "boot" "etc" "mnt" "srv" "usr" "var" "opt" ) bad_vars=( '$VERSION' '$ROOTDIR' '$NAME' '$DESC' ) #################### ## util functions ## #################### # adds a new warning to the counter add_warning(){ warnc=$((warnc+1)) } # cleans up the temp directoru clean_tempdir(){ rm -rf "${tmpdir}" } # recreates the temp directory make_tempdir(){ clean_tempdir mkdir "${tmpdir}" } # returns check status fail fail_check(){ clean_tempdir unset_indent error "${BOLD}Check ${RED}FAILED${RESET}" exit 1 } # returns check status fail success_check(){ clean_tempdir unset_indent if [ "${warnc}" == "0" ]; then success "Check ${GREEN}SUCCESS${RESET}" elif [ "${warnc}" == "1" ]; then success "Check ${GREEN}SUCCESS${RESET}${BOLD} with ${YELLOW}${warnc}${RESET}${BOLD} warning${RESET}" else success "Check ${GREEN}SUCCESS${RESET}${BOLD} with ${YELLOW}${warnc}${RESET}${BOLD} warnings${RESET}" fi exit 0 } # if the last command failed, print error and fail check_ret_fail(){ if [ $? -ne 0 ]; then if [ ! -z "$1" ]; then error "$1" fi fail_check fi } check_archive(){ make_tempdir info "Extracting the archive" tar xf "${archivepath}" -C "${tmpdir}" check_ret_fail "Failed to extract the archive" info "Checking archive files" set_indent for f in "${required_files[@]}"; do if [ ! -f "${tmpdir}/${f}" ]; then error "Archive does not contain required file: ${BOLD}${f}${RESET}" fail_check fi done unset_indent info "Checking DATA file" set_indent for k in "${required_keys[@]}"; do local line_1="$(grep "^${k}=" "${tmpdir}/DATA")" local line_2="$(grep "^${k} =" "${tmpdir}/DATA")" if [ -z "${line_1}" ] && [ -z "${line_2}" ]; then error "File does not contain the required key: ${BOLD}${k}${RESET}" fail_check fi if [ "${k}" == "version" ]; then if [ ! -z "${line_1}" ]; then version="$(echo "${line_1}" | sed 's/version= //g')" version="$(echo "${version}" | sed 's/version=//g')" else version="$(echo "${line_2}" | sed 's/version = //g')" version="$(echo "${version}" | sed 's/version =//g')" fi if [ -z "${version}" ]; then error "Failed to obtain package version information" fail_check fi fi done name="$(head -n1 "${tmpdir}/DATA" | sed 's/\[//g')" name="$(echo "${name}" | sed 's/]//g')" if [ -z "${name}" ]; then error "Failed to obtain package name information" fail_check fi filename="${name}_${version}.mpf" success "Check was completed" unset_indent info "Checking HASHES file" set_indent while read l; do if [ -z "${l}" ]; then continue fi local hash_line="$(echo "${l}" | cut -d' ' -f1)" local file_line="$(echo "${l}" | cut -d' ' -f3)" if [ -z "${hash_line}" ] || [ -z "${file_line}" ]; then error "File contains an invalid formatted line" fail_check fi if [ "${#hash_line}" != "32" ]; then error "File contains an invalid MD5 hash" fail_check fi done < "${tmpdir}/HASHES" success "Check was completed" unset_indent info "Checking files.tar.gz archive" set_indent while read p; do if [ "${p:0:1}" == "." ] || [ "${p:0:1}" == "/" ]; then error "Root file location is invalid (${p:0:1})" fail_check fi local path_root="$(echo "${p}" | cut -d/ -f1)" local found=0 for d in "${root_dirs[@]}"; do if [ "${path_root}" == "${d}" ]; then found=1 break fi done if [ "${found}" == "0" ]; then error "Package files contains an unknown root directory: ${path_root}" fail_check fi done < <(tar tf "${tmpdir}/files.tar.gz") success "Check was completed" unset_indent info "Checking INSTALL file" set_indent if [ -f "${tmpdir}/INSTALL" ] && ! grep -q . "${tmpdir}/INSTALL"; then warn "Package contains an empty install script" add_warning fi success "Check was completed" unset_indent info "Checking CHANGES file" set_indent if ! grep -q . "${tmpdir}/CHANGES"; then warn "Changes file is empty" add_warning fi success "Check was completed" unset_indent info "Checking archive name" set_indent archivefile="$(basename "${archivepath}")" if [ "${archivefile}" != "${filename}" ]; then warn "Package archive name is not ideal (${archivefile} -> ${filename})" add_warning fi success "Check was completed" unset_indent clean_tempdir } check_source(){ info "Checking the package script" set_indent if [ ! -f "${sourcepath}/pkg.sh" ]; then error "Package script does not exist" fail_check fi source "${sourcepath}/pkg.sh" check_ret_fail "Failed to source the package script" check_pkg_vars check_ret_fail if [ ${#DESC} -gt 200 ]; then error "Package description is too long (>200)" fail_check fi local desc_lower="${DESC,,}" local line_num=0 if [[ "${DESC}" == *"contains"* ]] || [[ "${DESC}" == *"provides"* ]]; then warn "Avoid using words such as \"contains\" or \"provides\" in the package description" add_warning fi if type INSTALL &>/dev/null; then for v in "${bad_vars[@]}"; do if type INSTALL | grep "${v}" &> /dev/null; then error "${v} used in the install script" fail_check fi done fi while read l; do line_num=$((line_num+1)) for v in "${bad_vars[@]}"; do if echo "${l}" | grep "${v}" &> /dev/null; then warn "${v} used without parenthesis on line ${line_num}" add_warning fi done if echo "${l}" | grep '&&' | grep -v 'cd ..' &> /dev/null; then warn "Unreliable use of \"&&\" on line ${line_num}" add_warning fi done < "${sourcepath}/pkg.sh" success "Check was completed" unset_indent info "Checking the changes file" set_indent if [ ! -f "${sourcepath}/changes.md" ]; then error "Package does not contain a changes file" fail_check fi if ! grep -q . "${sourcepath}/changes.md"; then warn "Changes file is empty" add_warning fi success "Check was completed" unset_indent } ################# ## main script ## ################# if [ -z "${1}" ]; then error "Please specify a package archive or source directory" exit 1 fi if [ ! -f "${1}" ] && [ ! -d "${1}" ]; then error "Specified path is invalid" exit 1 fi if [ -f "${1}" ]; then archivepath="$(realpath "${1}")" check_archive elif [ -d "${1}" ]; then sourcepath="$(realpath "${1}")" check_source fi success_check