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