#!/bin/sh -e # # This is a simple package manager written in POSIX 'sh' for # KISS Linux utlizing the core unix utilites where needed. # # The script runs with 'set -e' enabled. It will exit on any # non-zero return code. This ensures that no function continues # if it fails at any point. # # Keep in mind that this involves extra code in the case where # an error is optional or required. # # Where possible the package manager should "error first". # Check things first, die is necessary and continue if all is well. # # The code below conforms to shellcheck's rules. However, some # lint errors *are* disabled as they relate to unexpected # behavior (which we do expect). # # KISS is available under the MIT license. # # - Dylan Araps. die() { # Print a message and exit with '1' (error). printf '\033[31m!>\033[m %s\n' "$@" >&2 exit 1 } log() { # Print a message with a colorful arrow to distinguish # from other output. printf '\033[32m=>\033[m %s\n' "$@" } pkg_search() { # Figure out which repository a package belongs to by # searching for directories matching the package name # in $KISS_PATH/*. [ "$KISS_PATH" ] || \ die "\$KISS_PATH needs to be set." \ "Example: KISS_PATH=/packages/core:/packages/extra:/packages/xorg" \ "Repositories will be searched in the configured order." \ "The variable should work just like \$PATH." # Disable globbing with 'set -f' to ensure that the unquoted # variable doesn't expand into anything nasty. # shellcheck disable=2086,2046 { set -f set -- "$1" $(IFS=:; find $KISS_PATH -maxdepth 1 -name "$1") set +f } # A package may also not be found due to a repository not being # readable by the current user. Either way, we need to die here. [ -z "$2" ] && die "Package '$1' not in any repository." printf '%s\n' "$2" } pkg_list() { # List installed packages. As the format is files and # diectories, this just involves a simple for loop and # file read. # Changing directories is similar to storing the full # full path in a variable, only there is no variable as # you can access children relatively. cd "$KISS_ROOT/var/db/kiss" || \ die "KISS database doesn't exist or is inaccessible." # Optional arguments can be passed to check for specific # packages. If no arguments are passed, list all. As we # loop over '$@', if there aren't any arguments we can # just set the directory contents to the argument list. [ "$1" ] || set -- * # Loop over each version file and warn if one doesn't exist. # Also warn if a package is missing its version file. for pkg; do [ -d "$pkg" ] || { log "Package '$pkg' is not installed." return 1 } [ -f "$pkg/version" ] || { log "Warning: Package '$pkg' has no version file." return } read -r version release < "$pkg/version" && printf '%s\n' "${pkg%/*} $version-$release" done } args() { # Parse script arguments manually. POSIX 'sh' has no 'getopts' # or equivalent built in. This is rather easy to do in our case # since the first argument is always an "action" and the arguments # that follow are all package names. # Actions can be abbreviated to their first letter. This saves # keystrokes once you memorize themand it also has the side-effect # of "correcting" spelling mistakes assuming the first letter is # right. while [ "$1" ]; do case $1 in # Build the list of packages. b*) ;; # List installed packages. l*) shift pkg_list "$@" exit ;; # Print version and exit. v*) log "$kiss 0.1.10" exit ;; # Catch all invalid arguments as well as # any help related flags (-h, --help, help). *) 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." exit ;; esac done } main() { kiss=${0##*/} args "$@" } main "$@"