]>
Commit | Line | Data |
---|---|---|
1 | #!/bin/sh | |
2 | # TODO: add duplicate checker to seeif a pkg is in multiple virtual pkgs | |
3 | ||
4 | prog=$(basename $0) | |
5 | usage() { | |
6 | echo "\ | |
7 | apkv: Quickly add and remove packages to/from Alpine virtual packages. | |
8 | ||
9 | Usage: | |
10 | $prog add <packages> <virtual> | |
11 | Add <packages> to virtual package <virtual>. If the virtual package | |
12 | does not exist, it will be created. | |
13 | ||
14 | $prog del <packages> <virtual> | |
15 | Remove <packages> from virtual package <virtual>. Empty virtual | |
16 | packages are removed from world. The shorthand '$prog del * <virtual>' can | |
17 | be used to empty <virtual>. | |
18 | ||
19 | $prog list [-o | packages] | |
20 | List virtual packages and their contents. | |
21 | packages List these virtual packages. If empty, list all. | |
22 | -o Only list orphan packages that are not owned by any | |
23 | virtual package. Orphans are automatically shown when | |
24 | listing all packages. | |
25 | ||
26 | $prog help | |
27 | Show this help. | |
28 | ||
29 | Virtual packages must be prefixed with a dot to be managed with apkv. Virtual | |
30 | package names passed as arguments will automatically be prefixed with dots if | |
31 | they are not present. | |
32 | ||
33 | Methods can be shortened to their closest unambiguous forms, e.g. a, d, or l. | |
34 | However, due to implementation specifics, any string starting with those forms | |
35 | will work; that functionality is unspecified and is not guaranteed. | |
36 | ||
37 | For example, the following are equivalent, with the first being preferred: | |
38 | $prog add syslinux .virt | |
39 | $prog a syslinux virt | |
40 | $prog abcdefg syslinux virt | |
41 | ||
42 | " | |
43 | } | |
44 | ||
45 | prepend_dot() { | |
46 | case $1 in | |
47 | .*) printf "$1";; | |
48 | *) printf ".$1";; | |
49 | esac | |
50 | } | |
51 | ||
52 | show_reqs() { | |
53 | _pkg="$1" | |
54 | shift >/dev/null | |
55 | [ -z "$_pkg" ] && return 1 | |
56 | if [ $# -gt 0 ]; then | |
57 | # override reqs list for apkv add | |
58 | _list=$(echo "$(for x do echo $x; done)" | sort | uniq) | |
59 | else | |
60 | apk info -e "$_pkg" >/dev/null || return 1 | |
61 | _list=$(apk info -R "$_pkg" | awk 'NR > 1 && NF' | sort) | |
62 | fi | |
63 | echo -e "\e[1m$_pkg\e[0m:" $_list | |
64 | } | |
65 | ||
66 | method="$1" | |
67 | shift >/dev/null | |
68 | case $method in | |
69 | l*) method=list;; | |
70 | a*) method=add;; | |
71 | d*) method=del;; | |
72 | h*) usage; exit;; | |
73 | -*) usage; exit;; | |
74 | *) [ -n "$method" ] && echo "Invalid method $method" >&2; usage; exit 1;; | |
75 | esac | |
76 | ||
77 | if [ "$method" = list ]; then | |
78 | if [ $# -eq 0 -o "$1" = '-o' ]; then | |
79 | if [ "$1" = '-o' -a $# -gt 1 ]; then | |
80 | echo "$method with -o switch does not accept arguments" >&2 | |
81 | usage | |
82 | exit 1 | |
83 | fi | |
84 | # ignore getopt's --, we don't care if an arg begins with - anyway | |
85 | [ "$1" = '--' ] && switch >/dev/null | |
86 | ||
87 | # OPTIMIZE: getting deps of virtual packages is slow, it would be | |
88 | # great to use apk info -R from show_reqs in orphan loop | |
89 | all=$(apk info) | |
90 | # show packages if no args | |
91 | if [ $# -eq 0 ]; then | |
92 | for x in $(echo "$all" | grep '^\.'); do | |
93 | show_reqs "$(prepend_dot "$x")" | |
94 | done | |
95 | fi | |
96 | # set all child packages of virtual packages as owned | |
97 | for v in $(echo "$all" | grep '^\.'); do | |
98 | for c in $(apk info -R $v | tail +2 | grep -v '^\.'); do | |
99 | # NOTE: use tr because shell may not have ksh-derived ${c//} | |
100 | # XXX: package name may have illegal chars for variable name | |
101 | # past those already replaced, and replacing chars may | |
102 | # result in variable collision—but official apk repos do | |
103 | # not have any packages that would cause issues | |
104 | eval "__$(echo $c | tr -s '.:-' _)=1" | |
105 | done | |
106 | done | |
107 | # check world against owned packages | |
108 | # TODO: notify user if package is in world and also a virtual package | |
109 | orphans=$( | |
110 | for x in $(cat /etc/apk/world | grep -v '^\.'); do | |
111 | eval "[ \${__$( | |
112 | echo $x | tr -s '.:-' _ | tr -s '<>=' "\n" | head -1 | |
113 | )-0} -eq 0 ]" && echo $x | |
114 | done | |
115 | ) | |
116 | if [ -z "$orphans" ]; then | |
117 | echo "No orphan packages found." | |
118 | else | |
119 | show_reqs orphans $orphans | |
120 | fi | |
121 | else | |
122 | for x in $*; do | |
123 | show_reqs "$(prepend_dot "$x")" | |
124 | done | |
125 | fi | |
126 | exit | |
127 | fi | |
128 | ||
129 | ## add/del | |
130 | # get and validate packages | |
131 | pkgs= | |
132 | virt= | |
133 | for x; do | |
134 | if [ -n "$pkgs" ]; then | |
135 | pkgs="$pkgs $virt" | |
136 | elif [ -n "$virt" ]; then | |
137 | pkgs=$virt | |
138 | fi | |
139 | virt=$x | |
140 | done | |
141 | if [ -z "$pkgs" ]; then | |
142 | echo 'Not enough arguments' >&2 | |
143 | usage | |
144 | exit 1 | |
145 | fi | |
146 | virt=$(prepend_dot $virt) | |
147 | # exit if database locked (99) or no package found to delete from (1) | |
148 | apk info -e "$virt" >/dev/null | |
149 | code=$? | |
150 | if [ $code -eq 99 -o \( $method = del -a $code -ne 0 \) ]; then | |
151 | exit $code | |
152 | fi | |
153 | ||
154 | # we have validated our args, so let's prepare to run apk: get sudo command if | |
155 | # it exists and we are not already root | |
156 | # TODO: support doas | |
157 | if [ "$(id -u)" != 0 ] && command -v sudo >/dev/null 2>&1; then | |
158 | sudo=$(command -v sudo) | |
159 | fi | |
160 | ||
161 | # handle quick delete shorthand mentioned in help | |
162 | if [ $method = del -a '*' = "$pkgs" ]; then | |
163 | $sudo apk del "$virt" | |
164 | exit $? | |
165 | fi | |
166 | ||
167 | currpkgs=$(apk info -R "$virt" | awk 'NR > 1 && NF') | |
168 | case $method in | |
169 | # NOTE: $(echo) construct uses word splitting to remove newlines | |
170 | add) currpkgs="$(echo $currpkgs) $pkgs";; | |
171 | del) # XXX: there's probably a more efficient way to do this | |
172 | for x in $pkgs; do | |
173 | currpkgs=$(echo "$currpkgs" | grep -vFiw $x) | |
174 | done;; | |
175 | esac | |
176 | ||
177 | if [ -z "$currpkgs" ]; then | |
178 | # virtual package is empty | |
179 | $sudo apk del "$virt" | |
180 | code=$? | |
181 | else | |
182 | show_reqs "$virt" $currpkgs | |
183 | $sudo apk add -t "$virt" $currpkgs | |
184 | code=$? | |
185 | if [ $method = add -a $code -eq 0 ]; then | |
186 | # remove packages from world if they are being moved to a virtual one | |
187 | del= | |
188 | for x in $pkgs; do | |
189 | if apk info -e "$x" >/dev/null 2>&1; then | |
190 | del="$del $x" | |
191 | fi | |
192 | done | |
193 | if [ -n "$del" ]; then | |
194 | echo "Removing packages from world:$del" | |
195 | $sudo apk del $del | |
196 | fi | |
197 | fi | |
198 | fi | |
199 | # pass along sudo/apk exit code | |
200 | exit $code | |
201 | ||
202 | # vi:sw=4:sts=4:et |