#!/bin/sh # # kiss - package manager for kiss linux. die() { printf '\033[31m!>\033[m %s\n' "$@" >&2 exit 1 } log() { printf '\033[32m=>\033[m %s\n' "$@" } source_type() { [ -z "$1" ] && return 1 # No file. [ -f "$1" ] && return 2 # Local file. [ -f "$src_dir/${1##*/}" ] && return 3 # Cached downloaded file. [ -z "${1##git:*}" ] && return 4 # Git repository. [ -z "${1##*://*}" ] && return 5 # Remote file. } pkg_clean() { rm -rf -- "$mak_dir" "$pkg_dir" \ "$cac_dir/manifest-$$" "$cac_dir/tar" "$cac_dir/checksums-$$" } pkg_search() { set -f # shellcheck disable=2086,2046 set -- "$1" $(IFS=:; find $KISS_PATH -maxdepth 1 -name "$1") set +f [ -z "$2" ] && die "Package '$1' not in any repository." rep_dir=${2%/$1} } pkg_setup() { pkg_search "$1" cd "$rep_dir/$1" || die "'$rep_dir/$1' not accessible" [ -f sources ] || die "Sources file not found." [ -x build ] || die "Build file not found or not executable." [ -f licenses ] || die "License file not found or empty." read -r ver rel < version || die "Version file not found." pkg=${name:=$1}\#$ver-$rel.tar.gz } pkg_depends() { [ -f depends ] && while read -r dep opt; do pkg_list "$dep" || { [ "$1" = install ] && [ "$opt" = make ] && continue case $missing in *" $dep,"*) ;; *) missing="$missing $dep," pkg_setup "$dep" pkg_depends ;; esac } done < depends } pkg_sources() { src_dir=$cac_dir/sources/$name mkdir -p "$src_dir" while read -r src _; do case $(source_type "$src"; echo $?) in 4) git clone "${src##git:}" "$mak_dir" ;; 5) wget -P "$src_dir" "$src" || die "Failed to download $src." ;; 0|1) die "Source file '$src' not found." ;; esac done < sources } pkg_checksum() { while read -r src _; do case $(source_type "$src"; echo $?) in 2) src_path=$src ;; 3) src_path=$src_dir/${src##*/} ;; 4) continue esac (cd "${src_path%/*}" >/dev/null; sha256sum "${src##*/}") || die "Failed to generate checksums." done < sources > "${1-checksums}" } pkg_verify() { pkg_checksum "$cac_dir/checksums-$$" cmp -s "$cac_dir/checksums-$$" checksums || die "Checksum mismatch, run '$kiss checksum $name'." } pkg_extract() { while read -r src dest; do [ "$dest" ] && mkdir -p "$mak_dir/$dest" case $(source_type "$src"; echo $?)-$src in 2-*) cp -f "$src" "$mak_dir/$dest" ;; 3-*.tar*|3-*.tgz) tar xf "$src_dir/${src##*/}" -C "$mak_dir/$dest" \ --strip-components 1 || die "Couldn't extract ${src##*/}" ;; [01]-*) die "${src##*/} not found." esac done < sources } pkg_build() { (cd "$mak_dir"; "$OLDPWD/build" "$pkg_dir") || die "Build failed." cp -Rf "$rep_dir/$name" "$pkg_db" log "Sucessfully built $pkg." 2> "$pkg_db/$name/manifest" } pkg_strip() { log "Stripping unneeded symbols from binaries and libraries." find "$pkg_dir" -type f | while read -r binary; do case $(file -bi "$binary") in application/x-sharedlib*|application/x-pie-executable*) strip_opts=--strip-unneeded ;; application/x-archive*) strip_opts=--strip-debug ;; application/x-executable*) strip_opts=--strip-all ;; *) continue ;; esac strip "$strip_opts" "$binary" 2>/dev/null done } pkg_manifest() { # Store the file and directory list of the package. # Directories have a trailing '/' and the list is sorted in reverse. (cd "$pkg_dir" && find ./* -type d -exec printf '%s/\n' {} + -or -print) | sort -r | sed -e ss.ss | tee manifest > "$pkg_db/$name/manifest" } pkg_tar() { tar zpcf "$bin_dir/$pkg" -C "$pkg_dir" . || die "Failed to create package." log "Use '$kiss install $name' to install the package." } pkg_conflicts() { log "Checking for package conflicts." # Extract manifest from tarball and strip directories. tar xf "$bin_dir/$pkg" -O "./var/db/$kiss/$name/manifest" | while read -r line; do [ "${line%%*/}" ] && printf '%s\n' "$line" >> "$cac_dir/manifest-$$" done # Compare extracted manifest to all installed manifests. # If there are matching lines (files) there's a package # conflict. for db in "$sys_db"/*; do [ "$name" = "${db##*/}" ] && continue grep -Fxf "$cac_dir/manifest-$$" "$db/manifest" 2>/dev/null && die "Package '$name' conflicts with '${db##*/}'." done } pkg_install() { [ -f "$bin_dir/$pkg" ] || args b "$name" pkg_conflicts # Create a backup of 'tar' so it isn't removed during # package installation. cp "$(command -v tar)" "$cac_dir" log "Removing previous version of package if it exists." pkg_remove "$cac_dir/tar" kpxf "$bin_dir/$pkg" -C "$sys_dir/" 2>/dev/null "$sys_db/$name/post-install" 2>/dev/null log "Installed ${pkg%.tar.gz}" } pkg_remove() { pkg_list "${1:-${name-null}}" || return 1 # Create a backup of 'rm' and 'rmdir' so they aren't # removed during package removal. cp "$(command -v rm)" "$cac_dir" cp "$(command -v rmdir)" "$cac_dir" while read -r file; do [ "${file%/*}" = /etc ] && continue if [ -d "$sys_dir$file" ]; then "$cac_dir/rmdir" "$sys_dir$file" 2>/dev/null || continue else "$cac_dir/rm" -f -- "$sys_dir$file" || log "Failed to remove $file." fi && log "Removed $file" done < "$sys_db/${1:-$name}/manifest" # Use the backup of 'rm' to remove 'rmdir' and itself. "$cac_dir/rm" "$cac_dir/rmdir" "$cac_dir/rm" } pkg_updates() { for item in "$sys_db/"*; do pkg_search "${item##*/}" read -r db_ver db_rel < "$item/version" read -r re_ver re_rel < "$rep_dir/${item##*/}/version" [ "$db_ver-$db_rel" != "$re_ver-$re_rel" ] && printf '%s\n' "${item##*/} $re_ver-$re_rel" done } pkg_list() { [ "$1" ] && { [ -d "$sys_db/$1" ]; return "$?"; } for item in "$sys_db/"*; do read -r version release 2>/dev/null < "$item/version" && printf '%s\n' "${item##*/} $version-$release" done } args() { [ -w "$KISS_ROOT/" ] || case $1 in i*|r*) die "No write permissions to \$KISS_ROOT." esac case $1 in b*|c*|i*) pkg_setup "${2-null}"; esac case $1 in b*) [ -f checksums ] || die "Checksums missing, run '$kiss checksum $name'" pkg_depends [ -n "$missing" ] && die "Missing dependencies:${missing%,}" pkg_sources pkg_verify pkg_extract pkg_build [ -f nostrip ] || pkg_strip pkg_manifest pkg_tar ;; c*) pkg_sources pkg_checksum log "Generated checksums." ;; i*) pkg_depends install pkg_install ;; l*) pkg_list "$2" ;; r*) pkg_remove "${2-null}" || die "Package '${2-null}' not installed." ;; u*) pkg_updates ;; v*) log "$kiss 0.1.8" ;; *) log "$kiss [b|c|i|l|r|u] [pkg]" \ "build: Build a package." \ "checksum: Generate checksums." \ "install: Install a package (Runs build if needed)." \ "list: List packages." \ "remove: Remove a package." \ "update: Check for updates." esac } main() { trap pkg_clean EXIT INT kiss=${0##*/} sys_db=${sys_dir:=$KISS_ROOT}/var/db/$kiss [ -z "$KISS_PATH" ] && die "Set \$KISS_PATH to a repository location." mkdir -p "${cac_dir:=${XDG_CACHE_HOME:=$HOME/.cache}/$kiss}" \ "${mak_dir:=$cac_dir/build-$$}" \ "${bin_dir:=$cac_dir/bin}" \ "${pkg_db:=${pkg_dir:=$cac_dir/pkg-$$}/var/db/$kiss}" || die "Couldn't create directories." args "$@" } main "$@"