Documentation/scripts/pkgmeek

585 lines
24 KiB
Bash
Executable File

#!/bin/bash
#
# pkgmeek - a drop-in replacement for pkgmk
# based on upkgmk (c) 2018 therealfun
# and pkgmk (c) 2004--2022 Per Liden et al.
# Distributed under the same license,
# https://crux.nu/gitweb/?p=tools/pkgutils.git;a=blob_plain;f=COPYING;hb=HEAD
main() {
######################## main routine ################################
local o_ignored pkg_dir src_dir work _local_ here url u f TARGET pkg_utd
local errDL=0; local errUZ=0;
parse_options "$@"
[ "$PKGMK_RECURSIVE" = "yes" ] && recursive "$@"
#
# Exit early if cleaning was requested
#
if [ "$PKGMK_CLEAN" = "yes" ]; then
[ "$PKGMK_MTIME_ONLY" = "yes" ] && o_ignored="'-utd' "
[ "$PKGMK_UPDATE_FOOTPRINT" = "yes" ] && o_ignored+="'-uf' "
[ "$PKGMK_CHECK_SIG" = "yes" ] && o_ignored+="'-cs' "
[ "$PKGMK_REFRESH_SIG" = "yes" ] && refresh_signature
[ "$o_ignored" = "" ] || { warning "option -c nullifies these requested options:";
info "$o_ignored"; }
exec $PRTWASH_COMMAND -p -s -q "$(pwd)"
fi
#
# Read the Pkgfile to determine what to do next. But first ensure that
# it came from a trusted source (FS#1851)
#
[ "$PKGMK_UPDATE_SIG" = "yes" ] && [ -e ./Pkgfile ] || validate_pkgfile || exit $E_PKGFILE
[ -f .32bit ] && PKGMK_ARCH=32 || PKGMK_ARCH=64
. "Pkgfile"; . "$PKGMK_CONF"
# respect the settings for centralized source and package directories ...
[ -v pkg_dir ] || pkg_dir="$PKGMK_PACKAGE_DIR"/
[ -w "$pkg_dir" ] || pkg_dir="$(pwd)"/
[ -v src_dir ] || src_dir="$PKGMK_SOURCE_DIR"
[ -w "$src_dir" ] || src_dir=
# ... and for WORK_DIR in case the user wants to build in RAM
[ -v PKGMK_WORK_DIR ] && work="$PKGMK_WORK_DIR"
# Let the main routine and subsequent subroutines know what filenames
# they should expect to see on disk.
package="${name}#${version}-${release}.pkg.tar.${PKGMK_COMPRESSION_MODE}"
declare -a _local_
for (( s=0; s<${#source[@]}; s++ )); do
case "${source[$s]}" in
http://*|https://*|ftp://*|__git__*)
_local_[$s]="${source[$s]##*/}" # strip the leading path
# and for git sources, extract the project name:
[[ "${source[$s]}" =~ ^__git__ ]] && { _local_[$s]="${_local_[$s]%\#*}";
_local_[$s]="${_local_[$s]%.git}"; } ;;
*)
_local_[$s]="${source[$s]}" ;;
esac
[ -z "${renames[$s]}" ] || [ "${renames[$s]}" = "SKIP" ] || \
_local_[$s]="${renames[$s]}"
done
# Example: source = ( __git__https://gitlab.com/demo-user/cool-project.git#0.4.9
# https://dev.big-corp.com/src/needed-library.tgz
# random.patch )
# _local_ = ( cool-project
# needed-library.zgt <-- renamed to prevent automatic unpacking
# random.patch )
# The effective user should at least have write permissions on $PWD
[ -w "$(dirname "$work")" ] || work="$(pwd)"/work
rm -rf "$work"; mkdir -p "$work"/{src,pkg} && cd "$work"
# Skip the retrieval of sources if the user asked for '-utd' or '-uf'.
[[ "$PKGMK_MTIME_ONLY $PKGMK_UPDATE_FOOTPRINT" =~ yes ]] && [ -e "$PKGMK_PACKAGE_DIR/$package" ] \
|| { for (( u=0; u<${#_local_[@]}; u++ )); do
here="${_local_[$u]}"; url="${source[$u]}";
# at least one of the following commands should put a file of the
# appropriate name in the current directory
[ -e "$here" ] || [ "$src_dir" = "" ] || [ ! -e "$src_dir/$here" ] || ln -sf "$src_dir/$here" ;
[ -e "$here" ] || [ ! -e "$PKGMK_ROOT/$here" ] || ln -sf "$PKGMK_ROOT/$here" ;
[ -e "$here" ] || fetch_url "$url" "$here"; # <-- should also be able to resume partial downloads
[ -e "$here" ] || { error "failed to download $here. Check connection and try again."; errDL+=1; }
done ; }
[ $errDL = 0 ] && { [[ "$PKGMK_MTIME_ONLY $PKGMK_UPDATE_FOOTPRINT" =~ yes ]] || \
info "Successfully obtained all needed source files."; } || exit $E_DOWNLOAD
# Can stop here if the user asked for '-do', but honor any requests for '-um', '-uf',
# '-us', '-eo', or '-utd' by proceeding to those steps
[ "$PKGMK_DOWNLOAD_ONLY" = "no" ] || [ "$PKGMK_UPDATE_FOOTPRINT" = "yes" ] \
|| [ "$PKGMK_UPDATE_SIG" = "yes" ] || [ "$PKGMK_EXTRACT_ONLY" = "yes" ] \
|| [ "$PKGMK_MTIME_ONLY" = "yes" ] || { cleanup_work; exit 0; }
# If '-utd' was requested, check the modification times and then exit.
check_pkg_mtime; pkg_utd=$?
[ "$PKGMK_MTIME_ONLY" = "no" ] || [ "$PKGMK_CHECK_SIG" = "yes" ] || \
[ "$PKGMK_FORCE" = "yes" ] || { cleanup_work; exit $pkg_utd; }
# Take into account all the actions that can be done without extracting
# the downloaded sources
[ "$pkg_utd" = 0 ] || [ "$PKGMK_FORCE" = "yes" ] || [ "$PKGMK_EXTRACT_ONLY" = "yes" ] \
|| [[ "$PKGMK_UPDATE_FOOTPRINT $PKGMK_CHECK_SIG $PKGMK_UPDATE_SIG" =~ yes ]] \
|| { info "$package is up to date, use '-f' to force a rebuild."; cleanup_work; exit 0; }
# Silence the progress report if the user never intended to proceed with unpacking
[ "$pkg_utd" = 1 ] || [[ "$PKGMK_CHECK_SIG $PKGMK_UPDATE_SIG" =~ yes ]] || \
[[ "$PKGMK_IGNORE_SIG $PKGMK_UPDATE_FOOTPRINT" =~ yes ]] \
|| echo "Checking signatures before unpacking..."
# The option -uf is meant to be used *after* a previous invocation of pkgmeek has
# alerted the user to a footprint mismatch.
if [ "$PKGMK_UPDATE_FOOTPRINT" = "yes" ]; then
[ -f "$pkg_dir$package" ] || \
{ error "unable to update footprint. File '$package' not found.";
exit "$E_FOOTPRINT"; }
[ "$pkg_utd" = 1 ] || [ "$PKGMK_FORCE" = "yes" ] || \
{ error "outdated package. Use '-f' to force the footprint update.";
exit "$E_FOOTPRINT"; }
cat_footprint > "$PKGMK_ROOT/.footprint" && info "footprint created." \
|| { error "Failed to write the footprint."; cleanup_work; exit "$E_DIRPERM"; }
fi
# Updating signatures (option -us) requires only sources and footprint, not a built package.
if [ "$PKGMK_UPDATE_SIG" = "yes" ]; then
cat_signature > "$PKGMK_ROOT/.signature" && info "Signature successfully created." \
|| { info "Could not create signature."; cleanup_work; exit "$E_DIRPERM"; }
fi
# Exit after fulfilling any *explicit* requests for (signed) manifests
[[ "$PKGMK_UPDATE_FOOTPRINT $PKGMK_UPDATE_SIG" =~ yes ]] && { cleanup_work; exit 0; }
# All the sources should be here by now, let's verify that we can trust them.
readonly cs_fail_msg="Use '--ignore-signature' to override, if you have determined integrity by other means."
[ "$PKGMK_IGNORE_SIG" = "yes" ] || { check_signature "pre-build" | parse_signify_output;
case $? in
0) info "Sources successfully authenticated." ;;
1) error "Signature file missing or corrupted."
echo "$cs_fail_msg" ; cleanup_work; exit $E_SIGNATURE ;;
2) error "Failed to authenticate remote sources using signify."
echo "$cs_fail_msg" ; cleanup_work; exit $E_SIGNATURE ;;
esac; }
[ "$PKGMK_CHECK_SIG" = "no" ] || { cleanup_work; exit 0; } # no need to continue if the user only requested -cs
if [ "$pkg_utd" = 0 ] || [ "$PKGMK_FORCE" = "yes" ]; then
for (( u=0; u<${#_local_[@]}; u++ )) ; do
here="${_local_[$u]}"
if do_unpack "$here"; then
bsdtar -p -o -C src -xf "$here" || errUZ+=1
else
cp -r -L "$here" src/
fi
done
[ $errUZ = 0 ] && info "Sources successfully unpacked." || \
{ error "Failed to unpack all sources."; cleanup_work; exit "$E_UNPACK"; }
[ "$PKGMK_EXTRACT_ONLY" = "no" ] || exit 0
# The actual build step! (use fakeroot when building daemon ports as an ordinary user,
# otherwise the owner and group might not be correct)
(SRC=$(pwd)/src; PKG=$(pwd)/pkg; cd src; set -e -x; build)
[ $? = 0 ] && echo "Build successful. Moving on to compression." \
|| { error "Unsuccessful build!"; cleanup_work; exit "$E_BUILD"; }
[ -f "$PKGMK_ROOT/.nostrip" ] && ns_filter="| grep -v -f $PKGMK_ROOT/.nostrip"
find pkg -type f $ns_filter | while read -r f; do
case $(file -b "$f") in
*ELF*executable*not\ stripped*) strip --strip-all "$f" ;;
*ELF*shared\ object*not\ stripped*) strip --strip-unneeded "$f" ;;
current\ ar\ archive) strip --strip-debug "$f" ;;
esac
done
find pkg -type f -path "*/man/man*/*" | grep -v '.gz$' | xargs -r -I{} gzip -9 '{}'
find pkg -xtype l -path "*/man/man*/*" | while read f; do
TARGET="$(basename -s .gz $(readlink -n $f)).gz"; DIR="$(dirname ${f%%.gz}.gz)";
rm -f $f; [ -e $DIR/$TARGET ] && ln -sf $TARGET ${f%%.gz}.gz
done
[ $UID = 0 ] || fake_uid="--uid 0 --gid 0"
if (cd pkg; bsdtar --format=gnutar $fake_uid -cf "$pkg_dir$package" *); then
info "Package creation successful."
else
error "Unable to create the compressed package $package."
cleanup_work; exit "$E_BUILD"
fi
# Check the footprint of the built package, unless '-if' was given
[ "$PKGMK_IGNORE_FOOTPRINT" = "yes" ] || check_footprint
fi # Continue from here if extract and build were skipped
# Clean up the work directory.
find . -maxdepth 1 -mindepth 1 -type l -delete; cleanup_work
# Proceed to install/upgrade if requested.
if [ -n "$PKGMK_INSTALL_COMMAND" ]; then
[ $UID = 0 ] || PKGMK_SU="sudo";
[ -z "$PKGMK_SU" ] || [ -x "$(command -v $PKGMK_SU)" ] || PKGMK_SU="/usr/bin/doas";
[ -z "$PKGMK_SU" ] || [ -x "$(command -v $PKGMK_SU)" ] \
|| { error "Cannot run pkgadd as a non-root user."; exit "$E_INSTALL"; }
$PKGMK_SU $PKGMK_INSTALL_COMMAND "$pkg_dir$package" \
&& info "$(basename $PKGMK_INSTALL_COMMAND) $package succeeded." || \
{ error "$(basename $PKGMK_INSTALL_COMMAND) $package failed.";
exit "$E_INSTALL"; }
fi
# Done!
}
####################### error codes ##############################
E_PKGFILE=2 # invalid Pkgfile
E_DIRPERM=3 # (source/build) directory missing or missing read/write permission
E_DOWNLOAD=4 # error during download
E_UNPACK=5 # error during unpacking of source file(s)
E_FOOTPRINT=6 # footprint mismatch
E_BUILD=7 # error while running 'build()'
E_INSTALL=8 # error while installing the package via 'pkgadd'
E_SIGNATURE=9 # error verifying the signature
######################## settings ################################
readonly PKGMK_VERSION="#VERSION#"
readonly PKGMK_COMMAND="$0"
readonly PKGMK_ROOT="$PWD"
readonly PRTWASH_COMMAND="/usr/bin/prtwash"
PKGMK_DOWNLOAD_PROG="/usr/bin/curl"
PKGMK_GIT_COMMAND="/usr/bin/git"
PKGMK_CONF="/etc/pkgmk.conf"
PKGMK_SOURCE_DIR="$PWD"; PKGMK_WORK_DIR="$PWD/work"
PKGMK_PACKAGE_DIR="$PWD"; PKGMK_COMPRESSION_MODE="gz"
PKGMK_INSTALL_COMMAND=""; PKGMK_FORCE="no"
PKGMK_CLEAN="no"; PKGMK_KEEP_WORK="no"
PKGMK_DOWNLOAD_ONLY="no"; PKGMK_RECURSIVE="no"
PKGMK_EXTRACT_ONLY="no"; PKGMK_UPDATE_SIG="no"
PKGMK_MTIME_ONLY="no"; PKGMK_IGNORE_SIG="no"
PKGMK_UPDATE_FOOTPRINT="no"; PKGMK_REFRESH_SIG="no"
PKGMK_IGNORE_FOOTPRINT="no"; PKGMK_CHECK_SIG="no"
PKGMK_IGNORE_NEW="no"; PKGMK_PRIVATEKEY=""
######################## subroutines ################################
parse_options() {
while [ "$1" ]; do
case $1 in
-r|--recursive) PKGMK_RECURSIVE="yes" ;;
-c|--clean) [ -x "$PRTWASH_COMMAND" ] && PKGMK_CLEAN="yes" || \
{ error "option '$1' not supported ($PRTWASH_COMMAND not installed)";
exit 1; } ;;
-uf|--update-footprint) PKGMK_UPDATE_FOOTPRINT="yes" ;;
-us|--update-signature) PKGMK_UPDATE_SIG="yes" ;;
-rs|--refresh-signature) PKGMK_REFRESH_SIG="yes" ;;
-cs|--check-signature) PKGMK_CHECK_SIG="yes" ;;
--check-md5sum|--ignore-md5sum) ;; # deprecated flags
-d|-cm|-im|--download) ;; # but don't throw an error for them
-um|--update-md5sum) warn "updating md5sums is deprecated, ignoring option '$1'." ;;
-do|--download-only) PKGMK_DOWNLOAD_ONLY="yes" ;;
-eo|--extract-only) PKGMK_EXTRACT_ONLY="yes" ;;
-utd|--up-to-date) PKGMK_MTIME_ONLY="yes" ;;
-if|--ignore-footprint) PKGMK_IGNORE_FOOTPRINT="yes" ;;
-in|--ignore-new) PKGMK_IGNORE_NEW="yes" ;;
-is|--ignore-signature) PKGMK_IGNORE_SIG="yes" ;;
-ns|--no-strip) echo ".*" >> "$PKGMK_ROOT/.nostrip" ;;
-f|--force) PKGMK_FORCE="yes" ;;
-kw|--keep-work) PKGMK_KEEP_WORK="yes" ;;
-i|--install) PKGMK_INSTALL_COMMAND="/usr/bin/pkgadd" ;;
-u|--upgrade) PKGMK_INSTALL_COMMAND="/usr/bin/pkgadd -u" ;;
-pk|--public-key)
[ "$2" ] && PKGMK_PUBLICKEY="$2" || { echo "$(basename "$PKGMK_COMMAND"): option $1 requires an argument";
exit 1; }
shift ;;
-sk|--secret-key)
[ "$2" ] && PKGMK_PRIVATEKEY="$2" || { echo "$(basename "$PKGMK_COMMAND"): option $1 requires an argument";
exit 1; }
shift ;;
-cf|--config-file)
[ "$2" ] && PKGMK_CONF="$2" || { echo "$(basename "$PKGMK_COMMAND"): option $1 requires an argument";
exit 1; }
shift ;;
-v|--version)
echo "$(basename "$PKGMK_COMMAND") (pkgutils) $PKGMK_VERSION"; exit 0 ;;
-h|--help)
print_help; exit 0 ;;
*)
echo "$(basename "$PKGMK_COMMAND"): invalid option $1"; exit 1 ;;
esac
shift
done
}
print_help() {
echo "usage: $(basename "$PKGMK_COMMAND") [options]"
echo "options: "
echo " -r, --recursive search for Pkgfiles under $PWD, and run $(basename "$PKGMK_COMMAND")"
echo " with the other given options inside each directory found"
echo " -c, --clean remove package and downloaded files"
echo " -i, --install build and install package"
echo " -u, --upgrade build and install package (as upgrade)"
echo " -do, --download-only stop after downloading all the necessary source file(s)"
echo " -eo, --extract-only stop after downloading and extracting source file(s)"
echo " -utd, --up-to-date report whether the built package is up to date, then exit"
echo " -us, --update-signature update signature of Pkgfile and sources, do not build"
echo " -cs, --check-signature verify the signatures, do not build"
echo " -if, --ignore-footprint build package without checking footprint"
echo " -in, --ignore-new build package, ignoring new files in a footprint mismatch"
echo " -is, --ignore-signature build package without checking signature"
echo " -uf, --update-footprint update footprint using result from last build"
echo " -rs, --refresh-signature create new signature and keep existing sha256 checksums"
echo " -sk, --secret-key <file> use <file> to sign the port"
echo " -pk, --public-key <file> check the port signature using public-key <file>"
echo " -ns, --no-strip do not strip executable binaries or libraries"
echo " -f, --force build package even if it appears to be up to date"
echo " -kw, --keep-work keep temporary working directory"
echo " -cf, --config-file <file> use alternative configuration file"
echo " -v, --version print version and exit"
echo " -h, --help print help and exit"
}
validate_pkgfile() { # called from within PKGMK_ROOT
local errcode kv
[ -f Pkgfile ] || { error "no Pkgfile found. $PKGMK_ROOT is not a valid ports directory."; return "$E_PKGFILE"; }
check_signature "pre-Pkgfile" | parse_signify_output
errcode=$?
[ "$errcode" = 0 ] || info "Use '-is' if you have independent confirmation of the port's integrity."
[ "$errcode" = 0 ] || [ "$PKGMK_IGNORE_SIG" = "yes" ] || return $E_SIGNATURE
# the environment should not be affected by sourcing the Pkgfile both here
# and in the later build, but to be safe we use a nested subshell
( . Pkgfile; kv=0; [ -n "$name" ] || kv+=1; [ -n "$version" ] || kv+=2; \
[ -n "$release" ] || kv+=4; [ "$(type -t build)" = "function" ] || kv+=8; \
echo $kv ) | check_reqvars
}
check_reqvars () {
local checksum nullvars;
read -r checksum
[ $((checksum & 1)) = 1 ] && nullvars=" 'name'"
[ $((checksum & 2)) = 2 ] && nullvars+=" 'version'"
[ $((checksum & 4)) = 4 ] && nullvars+=" 'release'"
[ $((checksum & 8)) = 8 ] && nullvars+=" 'build()'"
[ "$checksum" = 0 ] || { error "Pkgfile does not specify these required variables:"; \
info "$nullvars"; return "$E_PKGFILE"; }
}
check_pkg_mtime() { # can be called even if some sources are missing
local li=0; local utd=0; local msg="$package is not up to date."
if [ -f "$pkg_dir$package" ]; then
utd=1
while [ $li -lt ${#_local_[@]} ] && [ "$utd" = 1 ]; do
[ ! -e "${_local_[$li]}" ] || \
[ "$pkgdir$package" -nt $(realpath "${_local_[$li]}") ] || utd=0
li=$(( li+1 ))
done
[ ! -e "$PKGMK_ROOT/Pkgfile" ] || [ "$pkg_dir$package" -nt "$PKGMK_ROOT/Pkgfile" ] || utd=0
fi
[ $utd = 0 ] || msg="$package is up to date."
[ "$PKGMK_MTIME_ONLY" = "yes" ] && info "$msg"; return $utd
}
fetch_url() {
local u="$1"; local h="$2"; local finished=0; local giturl tag CLONE_ARGS
local REPO SAVE_AS OCONTINUE OOUT; local m=0
if [[ "$u" =~ ^__git__ ]]; then
# git must be installed in order to obtain such a source
[ -x "$PKGMK_GIT_COMMAND" ] || return 1;
#
giturl="${u#__git__}"
# Did the port maintainer specify a branch other than 'master'?
tag="${giturl##*\#}"
[ "$tag" = "$giturl" ] || { giturl="${giturl%\#*}";
CLONE_ARGS="-c advice.detachedHead=false --branch $tag"; }
#
# Has this project been downloaded before?
if [ -d "$src_dir/$h.partial" ]; then
ln -s "$src_dir/$h.partial" "$h"; cd "$h"
"$PKGMK_GIT_COMMAND" pull $giturl $tag
finished=$?; cd ..
else
"$PKGMK_GIT_COMMAND" clone "$giturl" $CLONE_ARGS "$h.partial"
finished=$?; [ "$src_dir" = "" ] || { mv "$h.partial" "$src_dir";
ln -s "$src_dir/$h.partial" "$h"; }
fi
return $finished;
else
case "$PKGMK_DOWNLOAD_PROG" in
*wget) OCONTINUE="-c"
OOUT=("--compression=none" "--passive-ftp" "--no-directories"
"--tries=3" "--waitretry=3" $PKGMK_WGET_OPTIONS -O) ;;
*curl) OCONTINUE="-C -"
OOUT=("-L" "-#" "--fail" "--ftp-pasv"
--retry 3 --retry-delay 3 $PKGMK_CURL_OPTIONS -o) ;;
*) SAVE_AS="/bin/false" ;;
esac
#
# start with the mirrors defined in pkgmk.conf, then go to the url found in the Pkgfile
while [ $m -le ${#PKGMK_SOURCE_MIRRORS[@]} ] && [ $finished = 0 ] \
&& [[ ! $SAVE_AS =~ false$ ]]; do
[ "${PKGMK_SOURCE_MIRRORS[m]}" = "" ] && um=$u || \
{ REPO=${PKGMK_SOURCE_MIRRORS[m]%/}; um=$REPO/${u##*/}; }
m=$(( m+1 ))
# interrupted downloads from a previous run should be put where wget or curl will find them
[ -f "$src_dir/$h.partial" ] && { ln -s "$src_dir/$h.partial" . ;
SAVE_AS="$PKGMK_DOWNLOAD_PROG $um $OCONTINUE ${OOUT[@]}"; } \
|| SAVE_AS="$PKGMK_DOWNLOAD_PROG $um ${OOUT[@]}"
if $SAVE_AS "$h.partial"; then
finished=1
[ "$src_dir" = "" ] || [ ! -w "$src_dir"/ ] || \
{ mv "$h.partial" "$src_dir/$h"; ln -sf "$src_dir/$h" . ; }
else # an interrupted download should not have its efforts destroyed by cleanup_work()
[ ! -s "$h.partial" ] || [ "$src_dir" = "" ] || \
[ ! -w "$src_dir"/ ] || mv "$h.partial" "$src_dir"
fi
done
fi
}
do_unpack() {
[[ " ${nounpack[*]} " =~ " $(basename $1) " ]] && return 1
[[ "$1" =~ \.(tar|tar.gz|tar.Z|tgz|tar.bz2|tbz2|tar.xz|txz|tar.lzma|tar.lz|7z|zip|rpm)$ ]] && return 0
return 1
}
cat_footprint() {
pkginfo --footprint "$pkg_dir$package" \
| sed "s|\tlib/modules/$(uname -r)/|\tlib/modules/<kernel-version>/|g" \
| sort -k 3
}
check_footprint() {
local CN CM
local TRUTH="$PKGMK_ROOT/.footprint"; local diffs=0; local severity=error;
[ -f "$pkg_dir$package" ] || \
{ error "$package not found. Cannot check footprint."; exit "$E_FOOTPRINT"; }
if [ -f "$TRUTH" ]; then
diff -w -t -U 0 <(sort "$TRUTH") <(cat_footprint | sort) | \
sed '/^@@/d; /^+++/d; /^---/d; s/^+/NEW /g; s/^-/MISSING /g' > ".footprint.diff"
if [ -s ".footprint.diff" ]; then
CN=$(grep -c ^NEW ".footprint.diff"); CM=$(grep -c ^MISSING ".footprint.diff")
[ "$PKGMK_IGNORE_MISSING" = "yes" ] || diffs=$CM
[ "$PKGMK_IGNORE_NEW" = "yes" ] || diffs=$(( diffs+CN ))
[ $diffs = 0 ] && severity=warning
$severity "footprint mismatch found:"; cat ".footprint.diff" >&2
fi
rm ".footprint.diff"
else
warning "footprint not found, creating new."; cat_footprint > "$TRUTH"
fi
[ $diffs = 0 ] || exit $E_FOOTPRINT
}
parse_signify_output() { # chomps the output of check_signature()
local signout signerr
[ "$PKGMK_IGNORE_SIG" = "yes" ] && return 0
while read -r signout; do
case "$signout" in
*"Pkgfile verification failed")
signerr=-1; error "Signature missing! Unable to authenticate the Pkgfile."
;;
*"verification failed")
signerr=1; error "Signature file corrupted or unreadable."
;;
*"FAIL")
signerr=2; error "Signature mismatch found:"
echo "$signout" | awk -F: '/FAIL/ {printf "MISMATCH %s\n", $1}'
;;
*"OK") signerr=0 ;;
esac
done
return $signerr
}
check_signature() { # called from $PKGMK_ROOT in the case "when"="pre-Pkgfile",
# otherwise called from within $work.
local reqfiles=(Pkgfile); local s=0; local when="$1";
if [ -f "$PKGMK_ROOT/.signature" ]; then
[ "$when" = "pre-Pkgfile" ] || reqfiles=(.footprint)
while [ "$when" = "pre-build" ] && [ "$s" -lt ${#_local_[@]} ]; do
[[ "${source[$s]}" =~ ^__git__ ]] || reqfiles+=("${_local_[$s]}")
s=$(( s+1 ))
done
for FILE in "${reqfiles[@]}"; do
[ -e "$FILE" ] || ln -sf "$PKGMK_ROOT/$FILE" .
done
if [ -f "$PKGMK_PUBLICKEY" ]; then
/usr/bin/signify -C -p "$PKGMK_PUBLICKEY" -x "$PKGMK_ROOT/.signature" \
"${reqfiles[@]}" 2>&1
else
/usr/bin/signify -C -x "$PKGMK_ROOT/.signature" \
"${reqfiles[@]}" 2>&1
fi
else
[ "$when" = "pre-Pkgfile" ] && echo "Pkgfile verification failed"
[ "$when" != "pre-Pkgfile" ] && echo "signature verification failed"
fi
}
cat_signature() {
local ordered si key pub
[ -e "$PKGMK_ROOT/.footprint" ] || warning "Footprint not found, signature will be incomplete."
for key in ~/.ssh/*.sec /etc/ports/*.sec; do
[ -e "$key" ] || continue # workaround for brain-dead shell globbing
pub="/etc/ports/$(basename "${key%.sec}.pub")"
if [ -e "$pub" ]; then
pub=$(readlink -f "$pub")
for f in "$PKGMK_ROOT/Pkgfile" "$PKGMK_ROOT/.footprint"; do
[ -e "$f" ] && ordered+=( "$f" )
done
for ((si=0; si < ${#source[@]}; si++)); do
# ignore git directories when writing the signature
[[ "${source[$si]}" =~ ^__git__ ]] || ordered+=("${_local_[$si]}"); done
sha256sum --tag "${ordered[@]}" \
| sed 's|^SHA256 (.*/\(.*\))\(.* = .*\)|SHA256 (\1)\2|' \
| /usr/bin/signify -S -e -x - -q -s "$key" -m - \
| sed "s|${key%.sec}.pub|$pub|"
break
fi
done
}
refresh_signature() {
if [ -e ".signature" ] && [ ! -w ".signature" ]; then
echo "error: .signature not writable."; return $E_DIRPERM
fi
local REPO
[ -n "$PKGMK_PRIVATEKEY" ] || REPO=$(dirname "$PWD" | sed 's|^.*/||; s|\.git$||;')
[ -n "$REPO" ] && PKGMK_PRIVATEKEY="/etc/ports/${REPO}.sec"
if ! tail -n +3 ".signature" | /usr/bin/signify -S -e -x - -q \
-s "$PKGMK_PRIVATEKEY" -m - > .signature.tmp ; then
rm .signature.tmp
error "Refreshing signature failed"
return $E_SIGNATURE
else
mv .signature.tmp .signature
fi
}
interrupted() {
error "Interrupted."; cleanup_work; exit 1
}
cleanup_work() {
[ "$PKGMK_KEEP_WORK" = "yes" ] || { cd "$PKGMK_ROOT"; rm -rf "$work"; }
}
recursive() {
local ARGS DIR FILE
[ "$PKGMK_CLEAN" = "no" ] || { find "$PKGMK_ROOT" -name Pkgfile -printf "%h\n" \
| xargs "$PRTWASH_COMMAND" -s -p -b -q; exit $? ; }
ARGS=$(echo "$@" | sed "s/--recursive//g; s/\s*-r\s*/ /g")
for FILE in $(find "$PKGMK_ROOT" -name Pkgfile | sort); do
DIR=$(dirname "$FILE")
[ -d $DIR ] && { info "Entering directory '$DIR'.";
(cd "$DIR" && "$PKGMK_COMMAND" $ARGS);
info "Leaving directory '$DIR'."; }
done
exit
}
info() {
echo "=======> $1"
}
warning() {
info "WARNING: $1" >&2
}
error() {
info "ERROR: $1" >&2
}
######################## end of subroutines ###########################
## Now ensure that they cannot be overwritten when sourcing Pkgfile ##
readonly -f main info warning error print_help parse_options validate_pkgfile \
check_reqvars check_pkg_mtime fetch_url cat_footprint check_footprint \
cat_signature check_signature parse_signify_output refresh_signature \
cleanup_work recursive
trap "interrupted" SIGHUP SIGINT SIGQUIT SIGTERM
export LC_ALL=C.UTF-8
main "$@"