#!/bin/sh # (C) 2023 - Albert S. # Simple, dumb .deb fetcher (and installer) for non-debian distros set -e set -u # can be overwritten by config file PUBKEY_PATH="/var/lib/debfetcher/pubkeys/" TEMPLATES_PATH="/var/lib/debfetcher/packages/" DB_PATH="/var/db/debfetcher/" CACHE_DIR="/tmp/debfetcher" KEEP_OLD=0 DEBFETCHER_INSTALL_DESTDIR="/" DEBFETCHER_UNPRIV_USER="debfetcher" DEBFETCHER_BIN_SYMLINK_DIR="/usr/bin" DEBFETCHER_CONFIG="${DEBFETCHER_CONFIG:-"/etc/debfetcher/conf"}" fail() { echo $@ >&2 exit 1 } unpriv() { if [ $(id -u) = "0" ] ; then first="$1" shift su ${DEBFETCHER_UNPRIV_USER} -s $(which "$first") -- $@ else $@ fi } ifroot() { CMD="$1" shift if [ $(id -u) = "0" ] ; then $CMD $@ fi } check_debfetcher_dependencies() { curl --version &>/dev/null || fail "curl is missing or broken" echo -e "1\n2" | sort -V &>/dev/null || fail "sort -V not available it seems" gpg --version &>/dev/null || fail "gpg is missing or broken" ar --version &>/dev/null || fail "ar is missing or broken" tar --version &>/dev/null || fail "tar is missing or broken" patchelf --version &>/dev/null || fail "patchelf is missing or broken" } get_higher_version() { echo -e "$1\n$2" | sort -V | tail -n 1 } read_config() { if [ -f "${DEBFETCHER_CONFIG}" ] ; then source "${DEBFETCHER_CONFIG}" fi } print_usage() { echo "$0 get [package] - install/update the specified package" echo "$0 upgrade - check each package for updates and install them" } verify_sig() { set +e gpg --no-default-keyring --keyring "$1" --quiet --verify 2> "${CACHE_DIR}/last_gnupg_verify_result" if [ $? -ne 0 ] ; then fail "Signature check failed - See "${CACHE_DIR}/last_gnupg_verify_result"" fi set -e } # default presets for templates remove() { rm -rf -- "${DEBFETCHER_INSTALL_DESTDIR}/${THIS_BASEDIR}" } pre_install() { CURRENT_DIR="${DEBFETCHER_INSTALL_DESTDIR}${THIS_BASEDIR}" if [ -d "${CURRENT_DIR}" ] ; then mv -- "${DEBFETCHER_INSTALL_DESTDIR}${THIS_BASEDIR}" "${DEBFETCHER_INSTALL_DESTDIR}${THIS_BASEDIR}_${TS}" fi } post_install() { if [ ${KEEP_OLD} -eq 0 ] ; then rm -rf -- "${DEBFETCHER_INSTALL_DESTDIR}${THIS_BASEDIR}_${TS}" fi } debfetcher_install() { TEMPLATE_NAME=$(basename "$1") template="${TEMPLATES_PATH}/${TEMPLATE_NAME}.debfetcher" source $template DEB_PATH="$2" VERSION="$3" echo "${TEMPLATE_NAME}: Installing: version ${VERSION}, file: ${DEB_PATH}" TEMPDIR=$( mktemp -d -p "${CACHE_DIR}" ) ifroot chown "${DEBFETCHER_UNPRIV_USER}" "$TEMPDIR" cd "$TEMPDIR" mv "$DEB_PATH" . unpriv ar x "$( basename "$DEB_PATH")" mkdir data_contents ifroot chown "${DEBFETCHER_UNPRIV_USER}" data_contents datapkg="data.tar.xz" [ -f "$datapkg" ] || datapkg="data.tar.gz" [ -f "$datapkg" ] || datapkg="data.tar.bz2" [ -f "$datapkg" ] || fail "no data archive found in .deb" tar xf "$datapkg" -C data_contents cd data_contents #TODO: split doesn't have a benefit yet pre_install || fail "Pre-install failed" install || fail "Install failed" post_install || fail "Post-install failed" echo "$VERSION" > "/${DB_PATH}/${TEMPLATE_NAME}/version" } debfetcher_get() { TEMPLATE_NAME=$(basename "$1") template="${TEMPLATES_PATH}/${TEMPLATE_NAME}.debfetcher" [ -f "$template" ] || fail "Unknown package $1" source $template echo "${TEMPLATE_NAME}: Checking for new version..." INRELEASE_URL="${APTURL}/dists/${DISTRO}/InRelease" INRELEASE_CONTENT="$(unpriv curl -Ls $INRELEASE_URL)" echo "${TEMPLATE_NAME}: Verifying apt repository PGP signature..." echo "${INRELEASE_CONTENT}" | verify_sig "${PUBKEY}" # Fetch PACKAGES_SHA256SUM_SHOULD=$(echo "${INRELEASE_CONTENT}" | grep -E "${REPO}/binary-amd64/Packages$" | awk '{print $1}' | grep -E "^[0-9a-z]{64}$") PACKAGES_URL="${APTURL}/dists/${DISTRO}/${REPO}/binary-amd64/Packages" PACKAGES_CONTENT="$(unpriv curl -Ls "$PACKAGES_URL" && echo .)" PACKAGES_CONTENT="${PACKAGES_CONTENT%.}" PACKAGES_SHA256SUM_IS=$( echo -n "$PACKAGES_CONTENT" | sha256sum | awk '{print $1}' ) if [ "${PACKAGES_SHA256SUM_SHOULD}" != "${PACKAGES_SHA256SUM_IS}" ] ; then fail "${TEMPLATE_NAME}: Packages checksum do not match for $1: ${PACKAGES_SHA256SUM_SHOULD} and ${PACKAGES_SHA256SUM_IS} " fi NORMALIZED=$(echo "${PACKAGES_CONTENT}" | grep -E "(Package:|Filename:|SHA256:|Version:)" | tr '\n' ' ' | sed -e 's/Package:/\nPackage:/g') LATEST_VERSION=$( echo "${NORMALIZED}" | grep "Package: ${PACKAGE} " | sed -e 's/.*Version: //g' | awk '{print $1}' | sort -V | tail -n 1 ) CURRENT_VERSION="$( cat "${DB_PATH}/${TEMPLATE_NAME}/version" 2>/dev/null || true )" if [ -z "${CURRENT_VERSION}" ] ; then echo "${TEMPLATE_NAME}: First install of "$TEMPLATE_NAME", version $LATEST_VERSION" else if [ "$CURRENT_VERSION" != "$LATEST_VERSION" ] ; then HIGHER=$(get_higher_version "$CURRENT_VERSION" "$LATEST_VERSION") if [ "$HIGHER" != "$LATEST_VERSION" ] ; then fail "Local version is newer than repo version" fi echo "${TEMPLATE_NAME}: Will upgrade "$TEMPLATE_NAME" from "$CURRENT_VERSION" to $LATEST_VERSION" else echo "${TEMPLATE_NAME}: Already up to date" return fi fi FILENAME=$( echo "${NORMALIZED}" | grep "Package: ${PACKAGE} " | grep "Version: $LATEST_VERSION" | sed -e 's/.*Filename: //g' | awk '{print $1}' ) FILENAME_BASENAME=$(basename "${FILENAME}") DEB_URL="${APTURL}/${FILENAME}" DEB_TARGET_PATH="${CACHE_DIR}/${FILENAME_BASENAME}" touch "${DEB_TARGET_PATH}" ifroot chown "${DEBFETCHER_UNPRIV_USER}" "${DEB_TARGET_PATH}" echo "${TEMPLATE_NAME}: Fetching .deb..." unpriv curl -Ls -o - "${DEB_URL}" > "${DEB_TARGET_PATH}" || fail "Fetch failure" echo "${TEMPLATE_NAME}: Verifying checksums..." DEB_HASH_MUST=$(echo "${NORMALIZED}" | grep "Package: ${PACKAGE} " | grep "Version: $LATEST_VERSION" | sed -e 's/.*SHA256: //g' | awk '{print $1}' ) DEB_HASH_IS=$(sha256sum "${DEB_TARGET_PATH}" | awk '{print $1}' ) if [ "${DEB_HASH_IS}" != "${DEB_HASH_MUST}" ] ; then fail "${TEMPLATE_NAME}: .deb checksum mismatch for $TEMPLATE_NAME, file ${DEB_TARGET_PATH}: ${DEB_HASH_IS}, ${DEB_HASH_MUST}" fi debfetcher_install "${TEMPLATE_NAME}" "${DEB_TARGET_PATH}" "${LATEST_VERSION}" } init_db() { for template in ${TEMPLATES_PATH}/* ; do basename=$(basename "$template" | sed -e 's/.debfetcher//g') mkdir -p "${DB_PATH}/${basename}" done } init_cache() { mkdir -p "${CACHE_DIR}" ifroot chown root:root "${CACHE_DIR}" ifroot chmod o=--- "${CACHE_DIR}" rm -rf -- ${CACHE_DIR}/* } check_debfetcher_dependencies read_config init_db init_cache [ -w "${DEBFETCHER_INSTALL_DESTDIR}" ] || fail "No write access in install destdir" mkdir -p "${DB_PATH}" if [ $# -lt 1 ] ; then print_usage exit 1 fi CMD="$1" if [ "$CMD" = "get" ] ; then if [ $# -lt 2 ] ; then echo "$0 get [package]" exit 1 fi PACKAGE="$2" debfetcher_get "${PACKAGE}" fi if [ "$CMD" = "upgrade" ] ; then for template in ${TEMPLATES_PATH}/* ; do template=$(basename "${template}" | sed -e 's/.debfetcher//g' ) if [ -f "${DB_PATH}/${template}/version" ] ; then debfetcher_get ${template} fi done fi exit 0