shellcheck fixes, including real bug
[distro-functions] / src / package-manager-abstractions
1 #!/bin/bash
2 # I, Ian Kelling, follow the GNU license recommendations at
3 # https://www.gnu.org/licenses/license-recommendations.en.html. They
4 # recommend that small programs, < 300 lines, be licensed under the
5 # Apache License 2.0. This file contains or is part of one or more small
6 # programs. If a small program grows beyond 300 lines, I plan to switch
7 # its license to GPL.
8
9 # Copyright 2024 Ian Kelling
10
11 # Licensed under the Apache License, Version 2.0 (the "License");
12 # you may not use this file except in compliance with the License.
13 # You may obtain a copy of the License at
14
15 # http://www.apache.org/licenses/LICENSE-2.0
16
17 # Unless required by applicable law or agreed to in writing, software
18 # distributed under the License is distributed on an "AS IS" BASIS,
19 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 # See the License for the specific language governing permissions and
21 # limitations under the License.
22
23 # basic yum/apt package manager abstraction, plus a few minor conveniences
24 if command -v yum &> /dev/null; then
25 # package manager
26 p() {
27 local s; [[ $EUID != 0 ]] && s=sudo
28 $s yum "$@"
29 }
30 # package install
31 pi() {
32 local s; [[ $EUID != 0 ]] && s=sudo
33 $s yum -y install "$@"
34 }
35 # package find
36 pfd() {
37 local s; [[ $EUID != 0 ]] && s=sudo
38 $s yum search "$@"
39 }
40 # package remove/uninstall
41 pu() {
42 local s; [[ $EUID != 0 ]] && s=sudo
43 $s yum autoremove "$@"
44 }
45 # shellcheck disable=SC2120
46 pup() { # upgrade
47 local s; [[ $EUID != 0 ]] && s=sudo
48 $s yum -y distro-sync full "$@"
49 }
50 # package list info
51 pl() {
52 yum info "$@"
53 }
54 pfile() {
55 yum whatprovides \*/$1
56 }
57
58 elif command -v apt-get &>/dev/null; then
59 plock-wait() {
60 local i
61 i=0
62 while fuser /var/lib/dpkg/lock &>/dev/null; do
63 sleep 1
64 i=$(( i+1 ))
65 if (( i > 300 )); then
66 echo "error: timed out waiting for /var/lib/dpkg/lock" >&2
67 return 1
68 fi
69 done
70 }
71 pcheck() {
72 for arg; do
73 if [[ $1 == -* ]]; then
74 shift
75 else
76 break
77 fi
78 done
79 if dpkg -s -- "$@" |& grep -Fx "Status: install ok installed" &>/dev/null; then
80 return 1
81 fi
82 return 0
83 }
84 pp() { # package policy
85 apt-cache policy $@
86 }
87 p() {
88 local s; [[ $EUID != 0 ]] && s=sudo
89 case $1 in
90 install)
91 $s apt-get --purge --auto-remove "$@"
92 ;;
93 *)
94 $s apt-get "$@"
95 ;;
96 esac
97 }
98 pupdate() {
99 local now t s f cachetime limittime; [[ $EUID != 0 ]] && s=sudo
100 # update package list if its more than an 2 hours old
101 f=/var/cache/apt/pkgcache.bin
102 if [[ -r $f ]]; then
103 cachetime=$(stat -c %Y $f )
104 else
105 cachetime=0
106 fi
107 now=$(date +%s)
108 limittime=$(( now - 60*60*2 ))
109 for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list; do
110 if [[ -r $f ]]; then
111 t=$(stat -c %Y $f )
112 if (( t > limittime )); then
113 limittime=$t
114 fi
115 fi
116 done
117 if (( cachetime > limittime )); then
118 $s apt-get update
119 fi
120 }
121 pi() {
122 pcheck "$@" || return 0
123 pupdate
124 if [[ $- != *i* ]]; then
125 echo pi "$*"
126 fi
127 if [[ $EUID == 0 ]]; then
128 DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@"
129 else
130 sudo DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@"
131 fi
132
133 }
134
135 pi-nostart() {
136 local ret=
137 pcheck "$@" || return 0
138 plock-wait
139 pupdate
140 local f=/usr/sbin/policy-rc.d
141 if [[ $- != *i* ]]; then
142 echo pi-nostart "$@"
143 fi
144 if [[ $EUID == 0 ]]; then
145 dd of=$f status=none <<EOF
146 #!/bin/sh
147 exit 101
148 EOF
149 chmod +x $f
150 DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@" || ret=$?
151 rm $f
152 else
153 sudo dd of=$f status=none <<EOF
154 #!/bin/sh
155 exit 101
156 EOF
157 sudo chmod +x $f
158 sudo DEBIAN_FRONTEND=noninteractive apt-get -y install --purge --auto-remove "$@" || ret=$?
159 sudo rm $f
160 fi
161 return $ret
162 }
163 # package find description
164 pfd() {
165 # package name and descriptions
166 apt-cache search "$@"
167 }
168 # package find file
169 pff() {
170 local s; [[ $EUID != 0 ]] && s=sudo
171 # nice aptitude search from emacs shell. package description width as
172 # wide as the screen, and package name field small aptitude
173 # manual can't figure out how wide emacs terminal is, of course
174 # it doesn't consult the $COLUMNS variable... and in a normal
175 # terminal, it makes the package name field ridiculously big
176 # also, remove that useless dash before the description
177 aptitude -F "%c%a%M %p %$((COLUMNS - 30))d" -w $COLUMNS search "$@"
178 }
179 pu() {
180 local s; [[ $EUID != 0 ]] && s=sudo
181 local needed=false
182 for arg; do
183 if dpkg -s -- "$arg" &>/dev/null; then
184 needed=true
185 break
186 fi
187 done
188 $needed || return 0
189 plock-wait
190 $s apt-get -y remove --purge --auto-remove "$@"
191 # seems slightly redundant, but it removes more stuff sometimes.
192 $s apt-get -y autoremove
193 }
194 # shellcheck disable=SC2120
195 pup() { # upgrade
196 plock-wait
197 pupdate
198 local s; [[ $EUID != 0 ]] && s=sudo
199 $s apt-get -y dist-upgrade --purge --auto-remove "$@"
200 $s apt-get -y autoremove
201 }
202 # package info
203 pl() {
204 if type -p aptitude &>/dev/null; then
205 aptitude show "$@"
206 else
207 apt-cache show "$@"
208 fi
209 }
210 pfile() {
211 # -a = search all repos
212 local -a arg all
213 all=false
214 case $1 in
215 -a)
216 all=true
217 shift
218 ;;
219 esac
220 local file=$1
221 # ucfq can tell us about config files which are not tracked
222 # with apt-file. but, for at least a few files I tested
223 # which are tracked with apt-file, ucfq doesn't show their
224 # package name. So, commenting this, waiting to find
225 # a config file only tracked by ucfq to see if it gives the
226 # package name and if I can identify this kind of file.
227 # if [[ $file == /* ]] && ! ucfq -w $file | grep ::: &>/dev/null; then
228 # ucfq $file
229
230 if [[ $file == /* ]]; then
231 dpkg -S "$file"
232 else
233 if ! $all; then
234 arg=(--filter-origins "$(positive-origins)")
235 fi
236 if [[ $file == /* ]]; then
237 apt-file "${arg[@]}" find -x /"$file"\$
238 update-alternatives --list "$file" 2>/dev/null
239 else
240 apt-file "${arg[@]}" find -x "$file"\$
241 fi
242 fi
243 }
244 pkgfiles() {
245 if dpkg -s "$1" &>/dev/null; then
246 dpkg-query -L $1 | while read -r l; do [[ -f $l ]] && printf "%s\n" "$l"; done
247 else
248 apt-file -x list "^$1$"
249 fi
250 }
251
252 elif command -v pacman &>/dev/null; then
253 p() {
254 pacaur "$@"
255 }
256 pi() {
257 pacaur -S --noconfirm --needed --noedit "$@"
258 }
259 pfd() {
260 pacaur -Ss "$@"
261 }
262 pu() {
263 pacaur -Rs --noconfirm "$@"
264 if p=$(pacaur -Qdtq); then
265 pacaur -Rs $p
266 fi
267 }
268 aurex() {
269 p="$1"
270 aur='https://aur.archlinux.org'
271 curl -s $aur/"$(curl -s "$aur/rpc.php?type=info&arg=$p" \
272 | jq -r .results.URLPath)" | tar xz
273 cd "$p"
274
275 }
276 pmirror() {
277 local s; [[ $EUID != 0 ]] && s=sudo
278 local x
279 x=$(mktemp)
280 curl -s "https://www.archlinux.org/mirrorlist/\
281 ?country=US&protocol=https&ip_version=4&ip_version=6&use_mirror_status=on" \
282 | sed -r 's/^[ #]*(Server *=)/\1/' > $x
283 if (( $(stat -c %s $x ) > 10 )); then
284 $s cp $x /etc/pacman.d/mirrorlist
285 rm $x
286 fi
287 }
288 # shellcheck disable=SC2120
289 pup() { # upgrade
290 local s; [[ $EUID != 0 ]] && s=sudo
291 # file_time + 24 hours > current_time
292 if ! (( $(stat -c%Y /etc/pacman.d/mirrorlist) + 60*60*24 > $(date +%s) ))
293 then
294 pmirror
295 fi
296 pacaur -Syu --noconfirm "$@"
297 }
298 # package info
299 pl() {
300 pacaur -Si "$@"
301 }
302 pfile() {
303 pkgfile "$1"
304 }
305 pkgfiles() {
306 if pacaur -Qs "^$1$" &>/dev/null; then
307 pacman -Ql $1
308 else
309 pkgfile -l $1
310 fi
311 }
312 fi