#!/bin/sh # TODO: add duplicate checker to see if a pkg is in multiple virtual pkgs prog=$(basename $0) usage() { echo "\ apkv: Quickly add and remove packages to/from Alpine virtual packages. Usage: $prog add Add to virtual package . If the virtual package does not exist, it will be created. $prog del Remove from virtual package . Empty virtual packages are removed from world. The shorthand '$prog del * ' can be used to empty , which is equivalent to 'apk del ', though with special handling of leading dots. $prog list [-o | packages] List virtual packages and their contents. packages List these virtual packages. If empty, list all. -o Only list orphan packages that are not owned by any virtual package. Orphans are automatically shown when listing all packages. $prog help Show this help. Virtual packages must be prefixed with a dot to be managed with apkv. Virtual package names passed as arguments will automatically be prefixed with dots if they are not present. Methods can be shortened to their closest unambiguous forms, e.g. a, d, or l. However, due to implementation specifics, any string starting with those forms will work; that functionality is unspecified and is not guaranteed. For example, the following are equivalent, with the first being preferred: $prog add syslinux .virt $prog a syslinux virt $prog abcdefg syslinux virt " } prepend_dot() { case $1 in .*) printf "$1";; *) printf ".$1";; esac } show_reqs() { _pkg="$1" shift >/dev/null [ -z "$_pkg" ] && return 1 if [ $# -gt 0 ]; then # override reqs list for apkv add _list=$(echo "$(for x do echo $x; done)" | sort | uniq) else apk info -e "$_pkg" >/dev/null || return 1 _list=$(apk info -R "$_pkg" | awk 'NR > 1 && NF' | sort) fi echo -e "\e[1m$_pkg\e[0m:" $_list } method="$1" shift >/dev/null case $method in l*) method=list;; a*) method=add;; d*) method=del;; h*) usage; exit;; -*) usage; exit;; *) [ -n "$method" ] && echo "Invalid method $method" >&2; usage; exit 1;; esac if [ "$method" = list ]; then if [ $# -eq 0 -o "$1" = '-o' ]; then if [ "$1" = '-o' -a $# -gt 1 ]; then echo "$method with -o switch does not accept arguments" >&2 usage exit 1 fi # ignore getopt's --, we don't care if an arg begins with - anyway [ "$1" = '--' ] && switch >/dev/null all=$(apk info | sort) # show packages if no args if [ $# -eq 0 ]; then for x in $(echo "$all" | grep '^\.'); do show_reqs "$(prepend_dot "$x")" done fi # set all child packages of virtual packages as owned # OPTIMIZE: getting deps of virtual packages is slow, it would be # great to use apk info -R from show_reqs in orphan loop # NOTE: since we don't have arrays in sh, set individual vars instead for v in $(echo "$all" | grep '^\.'); do for c in $(apk info -R $v | tail +2 | grep -v '^\.'); do # NOTE: use tr because shell may not have ksh-derived ${c//} # XXX: have not verified if package name may have more illegal # chars for variable name past those already replaced, and # replacing chars may result in variable collision—but # official apk repos do not have any package names that # would cause issues eval "__$(echo $c | tr -s '.:+-' _ | tr -s '<=~>@' "\n" | head -1)=1" done done # check world against owned packages # TODO: notify user if package is both in world and virtual package orphans=$( for x in $(cat /etc/apk/world | grep -v '^\.'); do eval "[ \${__$( echo $x | tr -s '.:+-' _ | tr -s '<=~>@' "\n" | head -1 )-0} -eq 0 ]" && echo $x done ) if [ -z "$orphans" ]; then echo "No orphan packages found." else show_reqs orphans $orphans fi else for x in $*; do show_reqs "$(prepend_dot "$x")" done fi exit fi ## add/del # get and validate packages pkgs= virt= for x; do if [ -n "$pkgs" ]; then pkgs="$pkgs $virt" elif [ -n "$virt" ]; then pkgs=$virt fi virt=$x done if [ -z "$pkgs" ]; then echo 'Not enough arguments' >&2 usage exit 1 fi virt=$(prepend_dot $virt) # exit if database locked (99) or no package found to delete from (1) apk info -e "$virt" >/dev/null code=$? if [ $code -eq 99 -o \( $method = del -a $code -ne 0 \) ]; then exit $code fi # we have validated our args, so let's prepare to run apk: get sudo command if # it exists and we are not already root # TODO: support doas if [ "$(id -u)" != 0 ] && command -v sudo >/dev/null 2>&1; then sudo=$(command -v sudo) fi # handle quick delete shorthand mentioned in help if [ $method = del -a '*' = "$pkgs" ]; then $sudo apk del "$virt" exit $? fi currpkgs=$(apk info -R "$virt" | awk 'NR > 1 && NF') case $method in # NOTE: $(echo) construct uses word splitting to remove newlines add) currpkgs="$(echo $currpkgs) $pkgs";; del) # XXX: there's probably a more efficient way to do this for x in $pkgs; do currpkgs=$(echo "$currpkgs" | grep -vFiw $x) done;; esac if [ -z "$currpkgs" ]; then # virtual package is empty $sudo apk del "$virt" code=$? else show_reqs "$virt" $currpkgs $sudo apk add -t "$virt" $currpkgs code=$? if [ $method = add -a $code -eq 0 ]; then # remove packages from world if they are being moved to a virtual one del= for x in $pkgs; do #apk_adb.c: [!]name[<,<=,<~,=,~,>~,>=,>,><]ver, @ for pinning if grep -qE "^$x($|[<=~>@])" /etc/apk/world; then del="$del $x" fi done if [ -n "$del" ]; then echo "Removing packages from world:$del" $sudo apk del $del fi fi fi # pass along sudo/apk exit code exit $code # vi:sw=4:sts=4:et