diff --git a/mp-check/Makefile b/mp-check/Makefile new file mode 100644 index 0000000..e9b6186 --- /dev/null +++ b/mp-check/Makefile @@ -0,0 +1,9 @@ +PREFIX = /usr + +install: + install -Dm755 "mp-check.sh" $(DESTDIR)/$(PREFIX)/bin/mp-check + +uninstall: + rm $(DESTDIR)/$(PREFIX)/bin/mp-check + +.PHONY: install uninstall diff --git a/mp-check/README.md b/mp-check/README.md new file mode 100644 index 0000000..7ab7244 --- /dev/null +++ b/mp-check/README.md @@ -0,0 +1,15 @@ +# mp-check | MatterLinux package check script +A simple script to check MatterLinux package archives and sources +for common errors and mistakes. + +### Usage +To check a package archive, specify the path for the archive: +script: +```bash +mp-check /path/to/package_version.mpf +``` +You can also check for package sources files by specifying the +path for the package source directory: +```bash +mp-check /path/to/package/source +``` diff --git a/mp-check/mp-check.sh b/mp-check/mp-check.sh new file mode 100755 index 0000000..5a751fb --- /dev/null +++ b/mp-check/mp-check.sh @@ -0,0 +1,361 @@ +#!/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