forked from Botspot/pi-apps
-
Notifications
You must be signed in to change notification settings - Fork 0
/
api
executable file
·3151 lines (2581 loc) · 142 KB
/
api
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#!/bin/bash
#This script is the backbone of Pi-Apps. It hosts functions that all other scripts depend upon.
#By default, this file is hard to read. But there's an easy way to fix that. In your code editor, find the option to "Fold All".
#In Geany, this option can be found in the Document toolbar.
#Botspot has found it helpful to create a keyboard shortcut to do this quickly. You may find it helpful too.
#output functions below
error() { #red text and exit 1
echo -e "\e[91m$1\e[0m" 1>&2
exit 1
}
warning() { #yellow text
echo -e "\e[93m\e[5m◢◣\e[25m WARNING: $1\e[0m" 1>&2
}
status() { #cyan text to indicate what is happening
#detect if a flag was passed, and if so, pass it on to the echo command
if [[ "$1" == '-'* ]] && [ ! -z "$2" ];then
echo -e $1 "\e[96m$2\e[0m" 1>&2
else
echo -e "\e[96m$1\e[0m" 1>&2
fi
}
status_green() { #announce the success of a major action
echo -e "\e[92m$1\e[0m" 1>&2
}
generate_logo() { #display colorized Pi-Apps logo in terminal
#ANSI color codes: https://misc.flogisoft.com/bash/tip_colors_and_formatting
#Search for unicode characters: https://unicode-search.net - search for "block" and "quadrant"
# foreground colors
blue1='\e[38;5;75m'
blue2='\e[38;5;26m'
blue3='\e[38;5;21m'
blue4='\e[38;5;93m'
green='\e[38;5;46m'
darkgreen='\e[38;5;34m'
red='\e[38;5;197m'
white='\e[97m'
black='\e[30m'
default='\e[39m'
# background colors
bg_default='\e[49m'
bg_black='\e[40m'
bg_white='\e[107m'
#use simpler logo if OS is Buster or lower - to fix issue https://github.com/Botspot/pi-apps/issues/1441
local version_id="$(grep 'VERSION_ID=' /etc/os-release | tr -cd '0123456789.')"
if [[ "$version_id" != *\.* ]] && [ "$version_id" -ge 11 ];then
echo -e "${bg_default} ${green}🭊${darkgreen}🬹🬹🬹${green}🬿${default} ${darkgreen} ${default}
${blue1}🭇🬭🬭${green}\e[48;5;26m🬎${bg_default}${blue2}🬭🬭🬭${blue3}${green}\e[48;5;26m🬎${bg_default}${blue3}🬭🬭🬼${default} ${darkgreen} ${default}
${blue1}🮉${bg_black} ${red}▄ ▄ ▄${blue3} ${bg_default}▋${default} █▀▀🭍 ▄ ${darkgreen} ${black} ${darkgreen} ${black} ${darkgreen} ${default}
${blue2}🮉${bg_black} ${red}▄ ▄ ▄${blue3} ${bg_default}▋${default} █▄▄🭞 ▄ ${blue3}▄▄${default} 🭂▀▀█ █▀▀🭍 █▀▀🭍 🭂🬰🬰🬰 ${darkgreen} ${black} ${darkgreen} ${black} ${darkgreen} ${default}
${blue2}🮉${bg_black} ${red}▄ ▄ ▄${blue4} ${bg_default}▋${default} █ █ 🭓▄▄█ █▄▄🭞 █▄▄🭞 ▄▄▄🭞 ${darkgreen} ${black} ${darkgreen} ${default}
${blue3}🭦${bg_black}🭏🬭🬭🬭${blue4}🬭🬭🬭🬭🭄${bg_default}🭛${default} █ █ ${darkgreen} ${black} ${darkgreen} ${default}
\e[0m ${darkgreen} ${black} ${darkgreen} ${default}
${darkgreen} ${black} ${darkgreen} ${black} ${darkgreen} ${default}"
else
echo -e "${white}${bg_default} ${green}▅${darkgreen}▅▅▅${green}▅${default} ${darkgreen} ${default}
${blue1}▂▂▂${green}\e[48;5;26m\e[7m▂\e[27m${bg_default}${blue2}▂▂▂${blue3}${green}\e[48;5;26m\e[7m▂\e[27m${bg_default}${blue3}▂▂▂${white}${default} ${darkgreen} ${default}
${bg_black}${blue1}▌ ${red}▄ ▄ ▄${blue3} ▐${bg_default}${default} █▀▀▆ ▄ ${darkgreen} ${black} ${darkgreen} ${black} ${darkgreen} ${default}
${bg_black}${blue2}▌ ${red}▄ ▄ ▄${blue3} ▐${bg_default}${default} █▄▄\e[7m▂\e[27m ▄ ${blue3}▄▄${default} ▆▀▀█ █▀▀▆ █▀▀▆ ▆\e[7m━━━\e[27m ${darkgreen} ${black} ${darkgreen} ${black} ${darkgreen} ${default}
${bg_black}${blue2}▌ ${red}▄ ▄ ▄${blue4} ▐${bg_default}${default} █ █ \e[7m▂\e[27m▄▄█ █▄▄\e[7m▂\e[27m █▄▄\e[7m▂\e[27m ▄▄▄\e[7m▂\e[27m ${darkgreen} ${black} ${darkgreen} ${default}
${blue3}\e[7m▂\e[27m${bg_black}▃▃▃▃${blue4}▃▃▃▃▃${bg_default}\e[7m▂\e[27m${default} █ █ ${darkgreen} ${black} ${darkgreen} ${default}
\e[0m ${darkgreen} ${black} ${darkgreen} ${default}
${darkgreen} ${black} ${darkgreen} ${black} ${darkgreen} ${default}"
fi
}
#end of output functions
add_english() { #add en_US locale for more accurate error
if [ "$(cat /usr/share/i18n/SUPPORTED | grep -o 'en_US.UTF-8' )" == "en_US.UTF-8" ]; then
locale=$(locale -a | grep -oF 'en_US.utf8')
if [ "$locale" != 'en_US.utf8' ]; then
status "Adding en_US locale for better logging... "
sudo sed -i '/en_US.UTF-8/s/^#[ ]//g' /etc/locale.gen
sudo locale-gen
fi
else
warning "en_US locale is not available on your system. This may cause bad logging experience."
fi
export LANG="en_US.UTF-8"
export LANGUAGE="en_US.UTF-8"
export LC_ALL="en_US.UTF-8"
}
#package functions
package_info() { #list everything dpkg knows about the $1 package. Note: the package has to be installed for this to show anything.
local package="$1"
[ -z "$package" ] && error "package_info(): no package specified!"
#list lines in /var/lib/dpkg/status between the package name and the next empty line (empty line is then removed)
sed -n -e '/^Package: '"$package"'$/,/^$/p' /var/lib/dpkg/status | head -n -1
true #this may exit with code 141 if the pipe was closed early (to be expected with grep -v)
}
package_installed() { #exit 0 if $1 package is installed, otherwise exit 1
local package="$1"
[ -z "$package" ] && error "package_installed(): no package specified!"
#find the package listed in /var/lib/dpkg/status
#package_info "$package"
#directly search /var/lib/dpkg/status
grep "^Package: $package$" /var/lib/dpkg/status -A 1 | tail -n 1 | grep -q 'Status: install ok installed'
}
package_available() { #determine if the specified package-name exists in a repository
local package="$1"
[ -z "$package" ] && error "package_available(): no package name specified!"
#using find and grep to do this is nearly instantaneous, rather than apt-cache which takes several seconds
local IFS=$'\n'
for file in $(find /var/lib/apt/lists -maxdepth 1 -type f -name "*_Packages") ;do
grep -q "^Package: $package$" "$file" && break
done
}
package_dependencies() { #outputs the list of dependencies for the $1 package
local package="$1"
[ -z "$package" ] && error "package_dependencies(): no package specified!"
#find the package listed in /var/lib/dpkg/status
package_info "$package" | grep '^Depends: ' | sed 's/^Depends: //g'
}
package_latest_version() { #returns the latest available versions of the specified package-name. Doesn't matter if it's installed or not.
local package="$1"
[ -z "$package" ] && error "package_latest_version(): no package specified!"
# use slower but more accurate apt list command to get package version for current architecture
apt-cache policy "$package" 2>/dev/null | grep "Candidate: " | awk '{print $2}'
#grep -rx "Package: $package" /var/lib/apt/lists --exclude="lock" --exclude-dir="partial" --after 4 | grep -o 'Version: .*' | awk '{print $2}' | sort -rV | head -n1
}
package_is_new_enough() { #check if the $1 package has an available version greater than or equal to $2
local package="$1"
[ -z "$package" ] && error "package_is_new_enough(): no package specified!"
local compare_version="$2"
[ -z "$package" ] && error "package_is_new_enough(): no comparison version number specified!"
#determine the latest available version for the specified package
local package_version="$(package_latest_version "$package")"
#if version value not found, return 1 now
if [ -z "$package_version" ];then
return 1
fi
#given both the package_version and compare_version, see if the greater of the two is the available package's version
if [ "$(echo "$package_version"$'\n'"$compare_version" | sort -rV | head -n1)" == "$package_version" ];then
#if so, indicate success
return 0
else
return 1
fi
}
anything_installed_from_repo() { #Given an apt repository URL, determine if any packages from it are currently installed
[ -z "$1" ] && error "anything_installed_from_repo: A repository URL must be specified."
#user input repo-url. Remove 'https://', and translate '/' to '_' to conform to apt file-naming standard
local url="$(echo "$1" | sed 's+.*://++g' | tr '/' '_')"
#find all package-lists pertaining to the url
local repofiles="$(ls /var/lib/apt/lists/*_Packages | grep "$url")"
#for every repo-file, check if any of them have an installed file
local found=0
local IFS=$'\n'
local repofile
for repofile in $repofiles ;do
#search the repo-file for installed packages
grep '^Package' "$repofile" | awk '{print $2}' | while read -r package ;do
if package_installed "$package" ;then
echo "Package installed: $package"
exit 1
fi
done #if exit code is 1, search was successful. If exit code is 0, no packages from the repo were installed.
found=$?
if [ $found == 1 ];then
break
fi
done
#return an exit code
if [ $found == 1 ];then
return 0
else
return 1
fi
}
remove_repofile_if_unused() { #Given a sources.list.d file, delete it if nothing from that repository is currently installed. Deletion skipped if $2 is 'test'
local file="$1"
local testmode="$2"
local key="$3"
[ -z "$file" ] && error "remove_repo_if_unused: no sources.list.d file specified!"
#return now if the list file does not exist
[ -f "$file" ] || return 0
#determine what repo-urls are in the file
local urls="$(cat "$file" | grep -v '^#' | tr ' ' '\n' | grep '://')"
#there could be multiple urls in one file. Check each url and set the in_use variable to 1 if any packages are found
local IFS=$'\n'
local in_use=0
local url
for url in $urls ;do
if anything_installed_from_repo "$url" >/dev/null;then
in_use=1
break
fi
done
if [ "$in_use" == 0 ] && [ "$testmode" == test ];then
echo "The given repository is not in use and can be deleted:"$'\n'"$file" 1>&2
elif [ "$in_use" == 0 ];then
status "Removing the $(basename "$file" | sed 's/.list$//g') repo as it is not being used"
sudo rm -f "$file"
[ -f "$key" ] && sudo rm -f "$key" || true
fi
}
#apt functions
apt_lock_wait() { #Wait until other apt processes are finished before proceeding
#make sure english locale is added first
add_english
#check if sudo needs a password currently. this prevents sudo asking for a password in the below fuser command which would have "Waiting until APT locks are released... " written after it after 5 seconds
#the result would look like something in the terminal to a user "[sudo] password for USER: Waiting until APT locks are released... " which might be confusing
#this is very often the first command that pi-apps scripts run that requires sudo privileges from the user
#passwordless sudo (like on piOS) will always skip the contents of the if
if ! sudo -n true ; then
# sudo needs a password so prompt the user to give one before running other commands
sudo echo > /dev/null
fi
#in a background subprocess, after 5 seconds, say "Waiting until APT locks are released... "
(sleep 5; echo -n "Waiting until APT locks are released... ") &
local pid=$!
while [ ! -z "$(sudo fuser /var/lib/dpkg/lock /var/lib/apt/lists/lock /var/cache/apt/archives/lock /var/log/unattended-upgrades/unattended-upgrades.log /var/lib/dpkg/lock-frontend 2>/dev/null)" ];do
sleep 1
done
#Try to install a non-existent package to see if apt fails due to a lock-file. Repeat until no errors mention 'Could not get lock'
while sudo -E apt install lkqecjhxwqekc 2>&1 | grep -q 'Could not get lock' ;do
sleep 1
done
#If the background process finished, then that means the "waiting until" message was displayed. This means the kill command will return 1, so echo Done
kill $pid &>/dev/null || echo "Done"
}
less_apt() { #remove unwanted lines from apt output
grep --line-buffered -v "apt does not have a stable CLI interface.\|Reading package lists...\|Building dependency tree\|Reading state information...\|Need to get\|Selecting previously unselected package\|Preparing to unpack\|Setting up \|Processing triggers for \|^$"
}
apt_update() { #run an apt update with error-checking and minimal output
apt_lock_wait
status "Running \e[7msudo apt update\e[27m..."
output="$(sudo -E apt update --allow-releaseinfo-change "$@" 2>&1 | less_apt | tee /dev/stderr)"
exitcode=$?
status "apt update complete."
#inform user about autoremovable packages
if [ ! -z "$(echo "$output" | grep 'autoremove to remove them' )" ];then
echo -e "\e[33mSome packages are unnecessary.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt autoremove\e[0m."
fi
#inform user packages are upgradeable
if [ ! -z "$(echo "$output" | grep 'packages can be upgraded' )" ];then
echo -e "\e[33mSome packages can be upgraded.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt full-u\e[0mpg\e[4mrade\e[0m."
elif [ ! -z "$(echo "$output" | grep 'package can be upgraded' )" ];then
echo -e "\e[33mOne package can be upgraded.\e[39m Please consider running \e[4msudo a\e[0mp\e[4mt full-u\e[0mpg\e[4mrade\e[0m."
fi
#exit on apt error
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
if [ $exitcode != 0 ] || [ ! -z "$errors" ];then
echo -e "\e[91mFailed to run \e[4msudo apt update\e[0m\e[39m!"
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
#run some apt error diagnosis
echo "$output"
exit 1
fi
return 0
}
repo_add() { #add local packages to the /tmp/pi-apps-local-packages repository
#given local deb file(s), make a local apt repository in /tmp/pi-apps-local-packages
#see: https://unix.stackexchange.com/questions/87130/how-to-quickly-create-a-local-apt-repository-for-random-packages-using-a-debian
#and: https://serverfault.com/questions/447457/use-apt-get-source-on-a-debian-repo-without-using-etc-apt-source-list
#and: https://askubuntu.com/questions/382664/use-custom-directory-for-apt-get
#use this flag for apt commands to use the local repository: -o Dir::Etc::SourceList=/tmp/pi-apps-local-packages/source.list
#ensure the repo-folder exists
mkdir -p /tmp/pi-apps-local-packages || error "repo_add(): failed to create folder /tmp/pi-apps-local-packages"
#move every mentioned deb file to it
for file in "$@"; do
mv -f "$file" /tmp/pi-apps-local-packages || error "repo_add(): failed to move '$file' to the repository: /tmp/pi-apps-local-packages"
done
}
repo_refresh() { #index the pi-apps local apt repository
[ -d /tmp/pi-apps-local-packages ] || error "repo_update(): cannot index the repository - it's missing! /tmp/pi-apps-local-packages"
#index the repository by creating a Packages file
(cd /tmp/pi-apps-local-packages && apt-ftparchive packages . > Packages) || error "repo_update(): apt-ftparchive failed to index the repository: /tmp/pi-apps-local-packages
The Pi-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
#Make sure the Packages file actually exists
[ -f /tmp/pi-apps-local-packages/Packages ] || error "repo_update(): apt-ftparchive failed to index the repository: /tmp/pi-apps-local-packages
The Pi-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
#by default, apt-ftparchive will generate lines like "Filename: ./package-name". This seemed to have caused one error-report, so we remove the "./" to hopefully solve the problem.
sed -i 's+^Filename: \./+Filename: +g' /tmp/pi-apps-local-packages/Packages
# set repo origin name to pi-apps-local-packages - see PR #1986
echo 'APT::FTPArchive::Release {
Origin "pi-apps-local-packages";
};' > /tmp/pi-apps-local-packages/aptftp.conf
#hash the repository by creating a Release file
(cd /tmp/pi-apps-local-packages && apt-ftparchive -c=/tmp/pi-apps-local-packages/aptftp.conf release . > Release) || error "repo_update(): apt-ftparchive failed to hash the repository: /tmp/pi-apps-local-packages
The Pi-Apps developers have been receiving a few of these errors recently, but we can't figure out what the problem is without your help. Could you please reach out so we can solve this?"
#create a source.list for the repository - add pi-apps-local-packages to the top, then add lines from /etc/apt/sources.list
rm -f /tmp/pi-apps-local-packages/source.list
echo "deb [trusted=yes] file:/tmp/pi-apps-local-packages/ ./" > /tmp/pi-apps-local-packages/source.list
cat /etc/apt/sources.list >> /tmp/pi-apps-local-packages/source.list
# add priority to all pi-apps-local-packages so that they always have higher priority than repo versions - see PR #1986
# this has no affect when there the pi-apps-local-packages repo does not exist
echo 'Package: *
Pin: Release o=pi-apps-local-packages
Pin-Priority: 990' | sudo tee /etc/apt/preferences.d/pi-apps-local-packages >/dev/null
}
repo_rm() { #remove the local apt repository
#wait for other operations to finish before continuing - hopefully this will solve cases when the pi-apps local repository was removed unexpectedly by a second process
apt_lock_wait
rm -rf /tmp/pi-apps-local-packages || sudo rm -rf /tmp/pi-apps-local-packages || error "repo_rm(): failed to remove the local repository: /tmp/pi-apps-local-packages"
#Also remove broken symbolic link to /tmp/pi-apps-local-packages/./Packages
sudo rm -f /var/lib/apt/lists/_tmp_pi-apps-local-packages_._Packages
}
app_to_pkgname() { #given an app-name, convert it to a unique, valid package-name that starts with 'pi-apps-'
local app="$1"
[ -z "$app" ] && error "app_to_pkgname(): no app-name specified"
echo "pi-apps-$(echo "$app" | md5sum | cut -c1-8 | awk '{print $1}')"
}
install_packages() { #Make some packages dependencies of the $app app. Package-names, regex, filenames, and urls are supported.
#convert input array to newline-separated string
local IFS=' '
for arg in "$@"; do
local packages+="$arg
"
done
packages="${packages::-1}" #remove final empty newline
#the $app variable must contain something
[ -z "$app" ] && error 'install_packages function can only be used by apps to install packages. (the $app variable was not set)'
status "Will install these packages: $(tr '\n' ' ' <<<"$packages")"
#array-variable to store custom apt options (for local repositories)
local apt_flags=()
#variable to remember if the pi-apps-local-packages repository is being used
local using_local_packages=0
repo_rm #remove the local repo, just in case the last operation left it in an unrecoverable state.
#handle regex, urls, local packages
IFS=$'\n'
local package
for package in $packages ;do
#handle local packages (package-name starts with /)
if [[ "$package" == /* ]];then
#status "Handling local package $package"
[ -f "$package" ] || error "install_packages(): Local package does not exist! ($package)"
#determine the package name from the filename
packagename="$(dpkg-deb -I "$package" | grep "^ Package:" | awk '{print $2}')"
packageversion="$(dpkg-deb -I "$package" | grep "^ Version:" | awk '{print $2}')"
[ -z "$packagename" ] && error "install_packages(): failed to determine a package-name for the file '$package'"
[ -z "$packageversion" ] && error "install_packages(): failed to determine a package-version for the file '$package'"
#add this local package to the pi-apps-local-packages repository
repo_add "$package" || return 1
using_local_packages=1 #remember that the pi-apps-local-packages repository is being used
#replace package filename with name of package
packages="$(echo "$packages" | sed "s|$package|$packagename (>= $packageversion)|")"
#handle urls
elif [[ "$package" == *://* ]];then
#status "Handling url: $package"
local filename="/tmp/$(basename "$package")"
#add .deb extension if filename doesn't end with it.
if [ "${filename: -4}" != ".deb" ]; then
status "$filename is not ending with .deb, renaming it to '${filename}.deb'..."
local filename="${filename}.deb"
fi
wget -O "$filename" "$package" || return 1
#determine the package name from the filename
packagename="$(dpkg-deb -I "$filename" | grep "^ Package:" | awk '{print $2}')"
packageversion="$(dpkg-deb -I "$filename" | grep "^ Version:" | awk '{print $2}')"
[ -z "$packagename" ] && error "install_packages(): failed to determine a package-name for the file '$filename'"
[ -z "$packageversion" ] && error "install_packages(): failed to determine a package-version for the file '$filename'"
#add this local package to the pi-apps-local-packages repository
repo_add "$filename" || return 1
using_local_packages=1 #remember that the pi-apps-local-packages repository is being used
#replace package url with name of package
packages="$(echo "$packages" | sed "s|$package|$packagename (>= $packageversion)|")"
#expand regex (package-name contains *)
elif echo "$package" | grep -q '*' ;then
status "Expanding regex in '${package}'..."
list="$(apt-cache search "$package" | awk '{print $1}' | grep "$(echo "$package" | tr -d '*')")"
#replace package with expanded list
packages="$(echo "$packages" | grep -vF "$package")"$'\n'"$list"
fi
done
#now package list shouldn't contain any '*' characters, urls, local filepaths
if echo "$packages" | grep -q '*';then
error "install_packages(): failed to remove all regex from the package list:\n$packages"
elif [[ "$packages" == *://* ]];then
error "install_packages(): failed to remove all urls from the package list:\n$packages"
elif [[ "$packages" == */* ]];then
error "install_packages(): failed to remove all filenames from the package list:\n$packages"
fi #package list contains no '*' characters, urls, local filepaths
#change the $packages list from newline-delimited to comma-delimited
packages="$(tr '\n' ',' <<<"$packages")"
if [ "$using_local_packages" == 1 ];then
#Initialize the pi-apps-local-packages repository
repo_refresh || return 1
#add this repository to flags to add to apt
apt_flags+=(-o 'Dir::Etc::SourceList=/tmp/pi-apps-local-packages/source.list')
fi
status "Creating an empty apt-package to install the necessary apt packages..."
#to avoid issues with symbols and spaces in app names, we shasum the app name for use in apt
local package_name="$(app_to_pkgname "$app")"
echo "It will be named: $package_name"
#If this app's dummy deb is already installed, add its dependencies to the list.
#this allows install_packages() to be used multiple times in an app's script
if package_installed "$package_name" ;then
local existing_deps="$(package_dependencies "$package_name")"
status "The $package_name package is already installed. Inheriting its dependencies: $(echo "$existing_deps")"
packages+=",""$(echo "$existing_deps" | sed 's/, /,/g')"
fi
{ #create dummy apt package that depends on the packages this app requires
#this stores the comma-separated list of dependency packages. It removes duplicate entries.
local depends="$(echo "$packages" | sed 's/,|/ |/g' | sed 's/|,/| /g' | tr ',' '\n' | sort -u -t' ' -k1,1 | tr '\n' ',' | sed 's/^,//g' | sed 's/,$//g' | sed 's/,/, /g' ; echo)"
rm -rf ~/$package_name ~/$package_name.deb
mkdir -p ~/$package_name/DEBIAN
echo "Maintainer: Pi-Apps team
Name: $app
Description: Dummy package created by pi-apps to install dependencies for the '$app' app
Version: 1.0
Architecture: all
Priority: optional
Section: custom
Depends: $depends
Package: $package_name" > ~/$package_name/DEBIAN/control
#fix error report "dpkg-deb: error: control directory has bad permissions 700 (must be >=0755 and <=0775)"
#The two zeros fix error report "dpkg-deb: error: control directory has bad permissions 2755 (must be >=0755 and <=0775)"
sudo chmod -R '00755' ~/$package_name
#display the finished "Depends: " line to the user
grep --color=never "^Depends: " ~/$package_name/DEBIAN/control
}
#Skip installing the dummy deb if it is already installed and has an identical control file
if package_installed "$package_name" && [ "$(package_info "$package_name" | sort | grep -v '^Status: ')" == "$(cat ~/$package_name/DEBIAN/control | sort)" ];then
echo "$package_name is already installed and no changes would be made. Skipping..."
else
#Before apt update, check if local repo still exists
if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/pi-apps-local-packages/Packages ];then
error "User error: Uh-oh, the /tmp/pi-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
fi
#run an apt update
apt_update "${apt_flags[@]}" || exit 1
#After apt update, check again if local repo still exists
if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/pi-apps-local-packages/Packages ];then
error "User error: Uh-oh, the /tmp/pi-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
fi
#Build .deb file for the dummy package
local output="$(dpkg-deb --build ~/$package_name 2>&1)"
if [ $? != 0 ] || [ ! -f ~/$package_name.deb ];then
echo ""
echo "$output"
error "install_packages(): failed to create dummy deb ${package_name}!"
fi
#install dummy deb
status "Installing the $package_name package..."
apt_lock_wait
local output="$(sudo -E apt install -fy --no-install-recommends --allow-downgrades "${apt_flags[@]}" ~/$package_name.deb 2>&1 | less_apt | tee /dev/stderr)"
status "Apt finished."
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
if [ ! -z "$errors" ];then
echo -e "\e[91mFailed to install the packages!\e[39m"
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
echo "$output"
#some error reports seem to indicate that package URLs aren't being properly downloaded. This output aims to solve the mystery.
if [ "$using_local_packages" == 1 ] && [ ! -f /tmp/pi-apps-local-packages/Packages ];then
echo "User error: Uh-oh, the /tmp/pi-apps-local-packages folder went missing while installing packages.\nThis usually happens if you try to install several apps at the same time in multiple terminals."
elif [ "$using_local_packages" == 1 ] && echo "$output" | grep 'but it is not installable' ;then
echo -e "\e[91mThe Pi-Apps Local Repository was being used, and a package seemed to not be available. Here's the Packages file:\e[39m"
cat /tmp/pi-apps-local-packages/Packages
echo -e "Attempting apt --dry-run installation of the problematic package(s) for debugging purposes:\n"
grep "but it is not installable" <<<"$output" | awk '{print $4}' | uniq | xargs sudo -E apt install -fy --no-install-recommends --allow-downgrades --dry-run "${apt_flags[@]}"
fi
exit 1
fi
fi
rm -f ~/$package_name.deb
rm -rf ~/$package_name
#delete the local repository if it was used
if [ "$using_local_packages" == 1 ];then
repo_rm
fi
status_green "Package installation complete."
}
purge_packages() { #Allow dependencies of the $app app to be autoremoved.
#the $app variable must contain something
[ -z "$app" ] && error 'purge_packages function can only be used by apps to install packages. (the $app variable was not set)'
status "Allowing packages required by the $app app to be uninstalled"
#to avoid issues with symbols and spaces in app names, we shasum the app name for use in apt
local package_name="$(app_to_pkgname "$app")"
#if dummy deb found/installed
if package_installed "$package_name" ;then
echo "These packages were: $(package_dependencies "$package_name")"
status "Purging the $package_name package..."
apt_lock_wait
local output="$(sudo -E apt purge -y "$package_name" --autoremove 2>&1 | less_apt | tee /dev/stderr)"
status "Apt finished."
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
if [ ! -z "$errors" ];then
echo -e "\e[91mFailed to uninstall the packages!\e[39m"
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
#run some apt error diagnosis
echo "$output"
exit 1
fi
elif [ -f "${DIRECTORY}/data/installed-packages/${app}" ];then
#legacy pkg-install implementation
warning "Using the old implementation - an installed-packages file instead of a dummy deb"
local packages="$(cat "${DIRECTORY}/data/installed-packages/${app}" | tr '\n' ' ' | sed 's/ / /g')"
#normal mode
local output="$(sudo -E apt purge -y $packages 2>&1)"
exitcode=$?
errors="$(echo "$output" | grep '^[(E)|(Err]:')"
if [ $exitcode != 0 ] || [ ! -z "$errors" ];then
echo -e "\e[91mFailed to uninstall the packages!\e[39m"
echo -e "APT reported these errors:\n\e[91m$errors\e[39m"
#run some apt error diagnosis
echo "$output"
exit 1
fi
else
status "The $package_name package is not installed so there's nothing to do."
fi
status_green "All packages have been purged successfully."
rm -f "${DIRECTORY}/data/installed-packages/${app}"
}
get_icon_from_package() { #given a package-name, find all png files that it installed and print the one with the largest file-size.
[ -z "$1" ] && error "get_icon_from_package(): requires an apt package name"
#Find dependencies of the listed packages and scan them too
local package=''
local extra_packages=''
for package in "$@" ;do
#for every package specified, look for dependencies to that package that begin with the same name as the original package
#Example: given the 'shotwell' package, this will find the 'shotwell-common' package, as well as others
extra_packages+=" $(package_dependencies "$package" | sed 's/, \||/\n/g' | awk '{print $1}' | grep "^$package" | sort | uniq | tr '\n' ' ')"
done
dpkg-query -L "$@" $extra_packages 2>/dev/null | grep '\.png$\|\.svg$' | grep '/icons/\|/pixmaps/' | xargs wc -c | grep -v ' total' | sort -nr | head -n1 | sed 's/ / /g' | sed 's/^ //g' | tr ' ' '\n' | tail -n +2
}
ubuntu_ppa_installer() { #setup a PPA on an Ubuntu distro. Arguments: ppa_name
local ppa_name="$1"
[ -z "$1" ] && error "ubuntu_ppa_installer(): This function is used to add a ppa to a ubuntu based install but a required input argument was missing."
local ppa_grep="$ppa_name"
[[ "${ppa_name}" != */ ]] && local ppa_grep="${ppa_name}/"
local ppa_added=$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "$ppa_grep" | wc -l)
if [[ $ppa_added -eq "1" ]]; then
status "Skipping $ppa_name PPA, already added"
else
status "Adding $ppa_name PPA"
sudo add-apt-repository "ppa:$ppa_name" -y || exit 1
apt_update || exit 1
fi
# check if ppa .list filename does not exist under the current distro codename
# on a distro upgrade the .list filename is not updated and add-apt-repository can re-use the old filename
local ppa_dist="$__os_codename"
local standard_filename="/etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list"
if [[ ! -f "$standard_filename" ]] && ls /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-*.list 1> /dev/null; then
local original_filename="$(ls /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-*.list | head -1)"
# change the filename to match the current distro codename
sudo mv "$original_filename" "$standard_filename"
sudo rm -f "$original_filename".distUpgrade
sudo rm -f "$original_filename".save
fi
}
debian_ppa_installer() { #setup a PPA on a Debian distro. Arguments: ppa_name distribution key
local ppa_name="$1"
local ppa_dist="$2"
local key="$3"
[ -z "$1" ] || [ -z "$2" ] || [ -z "$3" ] && error "debian_ppa_installer(): This function is used to add a ppa to a debian based install but a required input argument was missing."
local ppa_grep="$ppa_name"
[[ "${ppa_name}" != */ ]] && local ppa_grep="${ppa_name}/ubuntu ${ppa_dist}"
local ppa_added=$(apt-get indextargets --no-release-info --format '$(SITE) $(RELEASE)' | sort -u | awk 'NF==2 {print}' | grep "$ppa_grep" | wc -l)
if [[ $ppa_added -eq "1" ]]; then
status "Skipping $ppa_name PPA, already added"
else
status "Adding $ppa_name PPA"
echo "deb https://ppa.launchpadcontent.net/${ppa_name}/ubuntu ${ppa_dist} main" | sudo tee /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list || error "Failed to add repository to sources.list!"
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys "$key"
if [ $? != 0 ];then
sudo rm -f /etc/apt/sources.list.d/${ppa_name%/*}-ubuntu-${ppa_name#*/}-${ppa_dist}.list
error "Failed to sign the $ppa_name PPA!"
fi
apt_update || exit 1
fi
}
adoptium_installer() {
status "Adding Adoptium repository:"
echo "- public key -> keyring"
rm -f /tmp/adoptium-public-key /tmp/adoptium-archive-keyring.gpg
wget -O /tmp/adoptium-public-key https://adoptium.jfrog.io/artifactory/api/security/keypair/default-gpg-key/public
gpg --no-default-keyring --keyring /tmp/adoptium-keyring.gpg --import /tmp/adoptium-public-key
rm -f /tmp/adoptium-public-key
echo " - keyring -> GPG key"
gpg --no-default-keyring --keyring /tmp/adoptium-keyring.gpg --export --output /tmp/adoptium-archive-keyring.gpg
rm -f /tmp/adoptium-keyring.gpg
echo " - Moving GPG key to /usr/share/keyrings"
sudo mv -f /tmp/adoptium-archive-keyring.gpg /usr/share/keyrings
echo " - Creating /etc/apt/sources.list.d/adoptium.list"
case "$__os_codename" in
bionic | focal | jammy | buster | bullseye | bookworm)
echo "deb [signed-by=/usr/share/keyrings/adoptium-archive-keyring.gpg] https://adoptium.jfrog.io/artifactory/deb $__os_codename main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
;;
*)
# use bionic target name explicitly for any other OS as its the oldest LTS target adoptium continues to support
# all supported adoptium OSs use the same debs so the target specified does not actually matter
echo "deb [signed-by=/usr/share/keyrings/adoptium-archive-keyring.gpg] https://adoptium.jfrog.io/artifactory/deb bionic main" | sudo tee /etc/apt/sources.list.d/adoptium.list >/dev/null
;;
esac
apt_update
if [ $? != 0 ]; then
anything_installed_from_repo "https://adoptium.jfrog.io/artifactory/deb"
if [ $? != 0 ]; then
# nothing installed from repo, this check is to prevent removing repos which other pi-apps scripts or the user have used successfully
# safe to remove
sudo rm -f /etc/apt/sources.list.d/adoptium.list /usr/share/keyrings/adoptium-archive-keyring.gpg
fi
error "Failed to perform apt update after adding Adoptium repository."
fi
}
pipx_install() {
# install pipx keeping in mind distro issues
# pipx <= 0.16.1 is compatible with 3.7 <= python3 < 3.9
# pipx >= 0.16.1 is compatible with python3 >= 3.7
# some distros lack pipx entirely
# some distros (raspbian bullseye specifically) have incompatible combinations of pipx (0.12.3) and python3 (3.9) versions, necessitating pipx to be installed/upgraded from pip
# pipx 1.0.0 is the first stable release and has some features that we would like to assume are available, install it from pip if the distro package is too old
if package_available pipx && package_is_new_enough pipx 1.0.0 ;then
install_packages pipx python3-venv || exit 1
elif package_is_new_enough python3 3.7 ; then
install_packages python3-venv || exit 1
sudo -H python3 -m pip install --upgrade pipx || exit 1
elif package_available python3.8 ;then
install_packages python3.8 python3.8-venv || exit 1
sudo -H python3.8 -m pip install --upgrade pipx || exit 1
else
error "pipx is not available so cannot install powerline-shell to python venv"
fi
sudo PIPX_HOME=/usr/local/pipx PIPX_BIN_DIR=/usr/local/bin pipx install "$@" || error "Failed to install $* with pipx"
}
pipx_uninstall() {
sudo PIPX_HOME=/usr/local/pipx PIPX_BIN_DIR=/usr/local/bin pipx uninstall "$@" || error "Failed to uninstall $* with pipx"
}
terminal_manage() { # wrapper for the original terminal_manage function to terminal_mange_multi
action="$1"
app="$2" #one app name
[ -z "$action" ] && error "terminal_manage(): Must specify an action: either 'install' or 'uninstall' or 'update' or 'refresh'"
terminal_manage_multi "$action $app"
}
terminal_manage_multi() { #function to install/uninstall/update/refresh multiple apps (or filelists) - uses a terminal and refreshes the app list
queue="$1" #one or multiple actions and app names (or filelists of the format 'filelist:path/to/file:path/to/another/file')
#To prevent multiple simultaneous manage instances, use the 'daemon' mode. This will create a queue of actions that are executed concurrently.
#The first daemon instance is the 'master' process. Subsequent processes will add the action to the queue and then exit.
#All output is generated by the 'master' daemon process. Subsequent processes shouldn't open a terminal, because the master one is already open.
if [ -f "${DIRECTORY}/data/manage-daemon/pid" ] && process_exists $(cat "${DIRECTORY}/data/manage-daemon/pid") ;then
#The 'master' daemon is already running. Avoid launching a second terminal.
"${DIRECTORY}/manage" daemon "$queue"
else
#in a terminal, first get the api functions, display the pi-apps logo, run the manage script, and refresh the app list if the $pipe variable is set
"${DIRECTORY}/etc/terminal-run" '
DIRECTORY="'"$DIRECTORY"'"
export geometry2="'"$geometry2"'"
source "${DIRECTORY}/api"
generate_logo
refresh_list() { #Refresh the current list of apps in the event of a change
if [ ! -z "'"$pipe"'" ] && [ -p "'"$pipe"'" ];then
echo -e "\f" > "'"$pipe"'"
"${DIRECTORY}/preload" yad "'"$prefix"'" > "'"$pipe"'" 2>/dev/null
fi
}
"${DIRECTORY}/manage" daemon "'"$queue"'"
#refresh app list
refresh_list
for i in {30..1} ;do
echo -en "You can close this window now. Auto-closing in $i seconds.\e[0K\r"
sleep 1
done
' "Terminal Output"
# Check if terminal-run failed to launch. GUI users don't see any terminal output if it fails (since there is no terminal open) so we need to prompt them with a GUI window
if [ "$?" != 0 ]; then
echo -e "Unable to open a terminal.\nDebug output below.\n$(DEBUG=1 "${DIRECTORY}/etc/terminal-run" 2>&1)" | yad --center --window-icon="${DIRECTORY}/icons/logo.png" \
--width=700 --height=300 --text-info --title="Error occured when calling terminal-run" \
--image="${DIRECTORY}/icons/error.png" --image-on-top --fontname=12 \
--button='OK'
if echo "$queue" | grep -q "^update filelist:" ;then #list of files separated by :
updatable_apps='' updatable_files="$(echo "$queue" | grep "^update filelist:" | sed 's/^update filelist://g' | tr ':' '\n')" no_status=true update_now_cli
fi
fi
fi
}
#end of apt functions
#flatpak functions
flatpak_install() { #install an app using flatpak
[ -z "$1" ] && error "flatpak_install(): This function is used to install a flatpak app, but nothing was specified."
#make sure flatpak is installed
if ! command -v flatpak >/dev/null ;then
error "flatpak_install(): Could not install $1 because flatpak is not installed!"
fi
if ! package_is_new_enough flatpak 1.14.4 ;then
case "$__os_codename" in
buster)
debian_ppa_installer "theofficialgman/flatpak-no-bwrap" "bionic" "0ACACB5D1E74E484"
apt_lock_wait
sudo apt --only-upgrade install flatpak -y | less_apt
;;
bullseye)
debian_ppa_installer "theofficialgman/flatpak-no-bwrap" "focal" "0ACACB5D1E74E484"
apt_lock_wait
sudo apt --only-upgrade install flatpak -y | less_apt
;;
bionic|focal|jammy)
ubuntu_ppa_installer "theofficialgman/flatpak-no-bwrap"
apt_lock_wait
sudo apt --only-upgrade install flatpak -y | less_apt
;;
esac
fi
status -n "Flatpak: Adding flathub remote... "
#Add the flathub remote, first as root, if that fails then try installing as user, while removing unwanted output
( sudo flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo || error "Flatpak failed to add flathub remote!" ) | grep --line-buffered -v "Note that the directories
'/var/lib/flatpak/exports/share'
'$HOME/.local/share/flatpak/exports/share'
are not in the search path set by the XDG_DATA_DIRS environment variable, so
applications installed by Flatpak may not appear on your desktop until the
session is restarted."
#exit 1 if above code failed
if [ ${PIPESTATUS[0]} != 0 ];then
exit 1
fi
status_green Done
status -n "Flatpak: installing $1... "
#Install the specified app, first as root, if that fails then try installing as user, while removing unwanted output
( sudo flatpak install flathub "$1" -y || flatpak install flathub "$1" -y || error "Flatpak failed to install $1!" ) | grep --line-buffered -v "Note that the directories
'/var/lib/flatpak/exports/share'
'$HOME/.local/share/flatpak/exports/share'
are not in the search path set by the XDG_DATA_DIRS environment variable, so
applications installed by Flatpak may not appear on your desktop until the
session is restarted."
#exit 1 if above code failed
if [ ${PIPESTATUS[0]} != 0 ];then
exit 1
fi
status_green Done
#Pi-Apps tries to avoid unnecessary reboots at all cost. Flatpak places desktop launchers in /var/lib/flatpak/exports/share/applications, which is not searched by default.
#This path is added to $XDG_DATA_DIRS on the next reboot, but we don't want to wait for that!
#If there are files in /var/lib/flatpak/exports/share/applications, and XDG_DATA_DIRS is missing flatpak paths, then bind-mount to /usr/share/applications
if [[ "$XDG_DATA_DIRS" != */var/lib/flatpak/exports/share* ]] && [ ! -z "$(ls /var/lib/flatpak/exports/share/applications)" ] && [ -z "$(ls /usr/share/applications/flatpak-temporary)" ];then
sudo mkdir -p /usr/share/applications/flatpak-temporary
sudo mount --bind /var/lib/flatpak/exports/share/applications /usr/share/applications/flatpak-temporary
elif [[ "$XDG_DATA_DIRS" == */var/lib/flatpak/exports/share* ]] ;then
sudo rm -rf /usr/share/applications/flatpak-temporary
fi
#Additionally, PiOS Buster had a bug where XDG_DATA_DIRS was missing flatpak's entries due to PiOS mods. Pi-Apps fixes this with a runonce.
true
}
flatpak_uninstall() { #uninstall an app using flatpak
[ -z "$1" ] && error "flatpak_uninstall(): This function is used to uninstall a flatpak app, but nothing was specified."
#if flatpak is not installed, then skip everything with code 0.
if ! command -v flatpak >/dev/null ;then
return 0
fi
#Only try to remove flatpak app if it's installed.
if flatpak list | grep -qF "$1" ;then
sudo flatpak uninstall "$1" -y || flatpak uninstall "$1" -y || error "Flatpak failed to uninstall $1!"
fi
}
#end of flatpak functions
#app functions
list_apps() { # $1 can be: installed, uninstalled, corrupted, cpu_installable, hidden, visible, online, online_only, local, local_only
if [ -z "$1" ] || [ "$1" == local ];then
#list all apps
ls "${DIRECTORY}/apps"
elif [ "$1" == all ];then
#combined list of apps, both online and local. Removes duplicate apps from the list.
echo -e "$(list_apps local)\n$(list_apps online)" | sort | uniq
elif [ "$1" == installed ];then
#list apps | only show ( list of installed apps | remove match string | basename )
list_apps local | list_intersect "$(grep -rx 'installed' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
elif [ "$1" == corrupted ];then
#list apps |only show ( list of corrupted apps | remove match string | basename )
list_apps local | list_intersect "$(grep -rx 'corrupted' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
elif [ "$1" == disabled ];then
#list apps | only show ( list of disabled apps | remove match string | basename )
list_apps local | list_intersect "$(grep -rx 'disabled' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
elif [ "$1" == uninstalled ];then
#list apps that have a status file matching "uninstalled"
list_apps local | list_intersect "$(grep -rx 'uninstalled' "${DIRECTORY}/data/status" | awk -F: '{print $1}' | sed 's!.*/!!')"
#also list apps that don't have a status file
list_apps local | list_subtract "$(ls "${DIRECTORY}/data/status")"
elif [ "$1" == have_status ];then
#list apps that have a status file
list_apps local | list_intersect "$(ls "${DIRECTORY}/data/status")"
elif [ "$1" == missing_status ];then
#list apps that don't have a status file
list_apps local | list_subtract "$(ls "${DIRECTORY}/data/status")"
elif [ "$1" == cpu_installable ];then
#list apps that can be installed on the device's OS architecture (32-bit or 64-bit)
#find all apps that have install-XX script, install script, or a packages file
find "${DIRECTORY}/apps" -type f \( -name "install-$arch" -o -name "install" -o -name "packages" \) | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq
elif [ "$1" == package ];then
#list apps that have a "packages" file
find "${DIRECTORY}/apps" -type f -name "packages" | sed "s+/packages++g" | sed "s+${DIRECTORY}/apps/++g" | sort | uniq
elif [ "$1" == standard ];then
#list apps that have scripts
find "${DIRECTORY}/apps" -type f \( -name "install-32" -o -name "install-64" -o -name "install" -o -name "uninstall" \) | sed "s+${DIRECTORY}/apps/++g" | sed 's+/.*++g' | sort | uniq
elif [ "$1" == hidden ];then
#list apps that are hidden
read_category_files | grep '|hidden' | awk -F'|' '{print $1}'
elif [ "$1" == visible ];then
#list apps that are in any other category but 'hidden', and aren't disabled
read_category_files | grep -v '|hidden' | awk -F'|' '{print $1}' # | list_subtract "$(list_apps disabled)"
elif [ "$1" == online ];then
#list apps that exist on the online git repo
if [ -d "${DIRECTORY}/update/pi-apps/apps" ];then
#if update folder exists, just use that
ls "${DIRECTORY}/update/pi-apps/apps" | grep .
else
#if update folder doesn't exist, then parse github HTML to get a list of online apps. Horrible idea, but it works!
wget -qO- "${repo_url}/tree/master/apps" | grep 'title=".*" data-pjax=' -o | sed 's/title="//g' | sed 's/" data-pjax=//g'
fi
elif [ "$1" == online_only ];then
#list apps that exist only on the git repo, and not locally
list_apps online | list_subtract "$(list_apps local)"
elif [ "$1" == local_only ];then
#list apps that exist only locally, and not on the git repo
list_apps local | list_subtract "$(list_apps online)"
else
error "list_apps(): unrecognized filter '$1'!"
fi
}
list_intersect() { #Outputs only the apps that appear in both stdin and in $1
# for example, the following two inputs will be a match
# Audacity
# Audacity
# while these two will NOT be a match
# Multimedia/Audacity
# .*/Audacity
comm -12 - <(echo "$1" | sort)
}
list_intersect_partial() { #Outputs only the apps that appear in both stdin and in $1 even with a partial match
# for example, the following two inputs will be a match
# Multimedia/Audacity
# .*/Audacity
# change \n to \| | remove last "\|"