forked from jumkey/redpill-load
-
Notifications
You must be signed in to change notification settings - Fork 0
/
ext-manager.sh
executable file
·1085 lines (934 loc) · 41.8 KB
/
ext-manager.sh
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
#!/usr/bin/env bash
set -u
##### BASIC RUNTIME VALIDATION #########################################################################################
# shellcheck disable=SC2128
if [ -z "${BASH_SOURCE}" ] ; then
echo "You need to execute this script using bash v4+ without using pipes"
exit 1
fi
cd "${BASH_SOURCE%/*}/" || exit 1
########################################################################################################################
##### CONFIGURATION YOU CAN OVERRIDE USING ENVIRONMENT #################################################################
readonly BRP_DEBUG=${BRP_DEBUG:-0} # whether you want to see debug messages
readonly MRP_SRC_NAME=${MRP_SRC_NAME:-$(basename "$0")}
readonly RPT_EXTS_DIR=${RPT_EXTS_DIR:-"$PWD/custom/extensions"}
########################################################################################################################
##### INCLUDES #########################################################################################################
. include/log.sh # logging helpers
. include/text.sh # text manipulation
. include/runtime.sh # need to include this early so we can used date and such
. include/json.sh # json parsing routines
. include/file.sh # file-related operations (copying/moving/unpacking etc)
. include/patch.sh # helpers for creating *.sh from template
########################################################################################################################
# Validates extension ID
#
# Args: $1 ID of an extension
# Exit: 0 on valid, 1 on validation failure
mrp_validate_id()
{
if [[ "${1}" =~ ^[A-Za-z]+[A-Za-z0-9._\-]+$ ]]; then
pr_dbg "ID \"%s\" is verified as valid" "${1}"
return 0
else
pr_err "The ID \"%s\" is invalid" "${1}"
return 1
fi
}
# Validates platform ID used to identify releases
#
# Args: $1 platform ID
# Exit: 0 on valid, 1 on validation failure
mrp_validate_platform_id()
{
if [[ "${1}" =~ ^[a-z0-9_]+$ ]]; then
pr_dbg "Platform ID \"%s\" is verified as valid" "${1}"
return 0
else
pr_err "Platform ID \"%s\" is invalid" "${1}"
return 1
fi
return 0;
}
# Args: $1 ID of an extension
mrp_has_extension()
{
if [[ -f "${RPT_EXTS_DIR}/${1}/${1}.json" ]]; then
return 0;
else
return 1;
fi
}
mrp_get_all_extensions()
{
rpt_list_directories "${RPT_EXTS_DIR}" $1
}
# Gets index file for an extension (if exists)
# The file path returned is guaranteed to be a valid index file
#
# Args: $1 ID of the extension | $2 silent mode to only return exit code and treat errors as debug [default=0]
# Return: file path
# Exit:
# 0 - success
# 1 - parameters passed are invalid
# 2 - file not found
# 3 - semantic validation failure (json struct, json semantic, etc)
mrp_get_existing_index_file()
{
pr_dbg "Loading existing index file for extension %s" "${1}"
if ! mrp_validate_id "${1}"; then
return 1
fi
local IDX_FILE="${RPT_EXTS_DIR}/${1}/${1}.json"
if [[ ! -r "${IDX_FILE}" ]]; then
if [[ "${2:-0}" -eq 1 ]]; then # bash is really fugly...
pr_dbg "Extension %s index file %s is not readable or does not exist - extension is probably not added" "${1}" "${IDX_FILE}"
else
pr_err "Extension %s index file %s is not readable or does not exist - extension is probably not added" "${1}" "${IDX_FILE}"
fi
return 2
fi
if ! mrp_validate_index_file "${IDX_FILE}" "${IDX_FILE}"; then
if [[ "${2:-0}" -eq 1 ]]; then # bash is really fugly...
pr_dbg "Extension %s index file %s is is not a valid index file" "${1}" "${IDX_FILE}"
else
pr_err "Extension %s index file %s is is not a valid index file" "${1}" "${IDX_FILE}"
fi
return 3
fi
pr_dbg "Found valid existing index for %s extension at %s" "${1}" "${IDX_FILE}"
echo "${IDX_FILE}"
return 0
}
# Args: $1 extension id
mrp_show_ext_info()
{
local index_file
local index_file_exit
local -A info_kv
index_file=$(mrp_get_existing_index_file "${1}" 1)
index_file_exit=$?
pr_info "========================================== %s ==========================================" "${1}"
case $index_file_exit in
0) brp_read_kv_to_array "${index_file}" 'info' info_kv
pr_info "Extension name: %s" "${info_kv[name]}"
if [[ ! -z ${info_kv[description]+x} ]]; then
pr_info "Description: %s" "${info_kv[description]}"
else
pr_dbg "Description: <not provided>"
fi
pr_info "To get help visit: %s" "${info_kv[help_url]}"
if [[ ! -z ${info_kv[packer_url]+x} ]]; then
pr_info "Extension preparer/packer: %s" "${info_kv[packer_url]}"
else
pr_dbg "Extension preparer/packer: <not provided>"
fi
if [[ ! -z ${info_kv[author_url]+x} ]]; then
pr_info "Software author: %s" "${info_kv[author_url]}"
else
pr_dbg "Software author: <not provided>"
fi
pr_info "Update URL: %s" "$(brp_json_get_field "${index_file}" 'url' 1)"
brp_read_kv_to_array "${index_file}" 'releases' info_kv
local platforms=''
for platform in "${!info_kv[@]}"; do
platforms+="${platform} "
done
pr_info "Platforms supported: %s" "${platforms}"
;;
1) pr_err "Extension ID %s is invalid" "${1}"
;;
2) pr_err "Extension %s is not installed" "${1}"
;;
3) pr_err "Extension %s index is invalid - try \"%s update\" to fix it" "${1}" "${MRP_SRC_NAME}"
;;
*) pr_err "Unknown error %d" "${index_file_exit}"
esac
pr_info "=======================================================================================\n"
}
# Returns standardized error of extension index validation failure
#
# Args: $1 extension index URL | $2 id of the extension (if known) | $2 error message
mrp_validate_ext_idx_fail()
{
pr_err "Extension loaded from %s (id: %s) is invalid: %s. Please report that to the extension maintainer via help URL" \
"${1}" "${2}" "${3}"
}
# Validates extension index file
#
# Args:
# $1 Path to the index file
# $2 URL or path of the index file (if known)
# $3 Verify entries [validate all URLs and post warnings if they're not valid, returning 2 if any are invalid]
# Exit: 0 - success; 1 - failure; 2 - soft-failure
mrp_validate_index_file()
{
pr_dbg "Validating extension index file \"%s\"" "${1}"
if ! brp_json_validate "${1}" 1; then # validate JSON *file*, not its format/semantic - it will be parsable
mrp_validate_ext_idx_fail "${2}" '' 'index JSON file is unparsable'
return 1
fi
local id=$(brp_json_get_field "${1}" 'id') # this will error-out if field doesn't exist at all
if ! mrp_validate_id "${id}"; then
mrp_validate_ext_idx_fail "${2}" "${id}" 'the ID did not pass validation'
return 1
fi
# todo validate URL optionally?
if [[ "$(brp_json_has_field "${1}" 'url')" -ne '1' ]]; then
mrp_validate_ext_idx_fail "${2}" "${id}" 'index has no update URL'
return 1
fi
if [[ "$(brp_json_has_field "${1}" 'info')" -ne '1' ]]; then
mrp_validate_ext_idx_fail "${2}" "${id}" 'index has no info section'
return 1
fi
local -A info_kv
brp_read_kv_to_array "${1}" 'info' info_kv
if [[ -z "${info_kv[name]+x}" ]] || [[ "${info_kv[name]}" == '' ]]; then
mrp_validate_ext_idx_fail "${2}" "${id}" 'index info section has no/empty name defined'
return 1
fi
if [[ -z "${info_kv[help_url]+x}" ]] || [[ "${info_kv[help_url]}" == '' ]]; then
mrp_validate_ext_idx_fail "${1}" "${id}" 'index info section has no/empty help_url defined'
return 1
fi
if [[ "$(brp_json_has_field "${1}" 'releases')" -ne '1' ]]; then
mrp_validate_ext_idx_fail "${2}" "${id}" 'index has no releases section'
return 1
fi
local -a releases
brp_json_get_keys "${1}" 'releases' releases
for rel_id in "${releases[@]}"; do
if ! mrp_validate_platform_id "${rel_id}"; then
mrp_validate_ext_idx_fail "${2}" "${id}" "release ID \"${rel_id}\" is invalid"
return 1
fi
done
pr_dbg "Extension index file \"%s\" OK" "${1}"
return 0
}
# Returns standardized error of extension recipe validation failure
#
# Args: $1 extension recipe URL | $2 id of the extension (if known) | $3 platform id | $4 error message
mrp_validate_ext_recipe_fail()
{
local ext_id;
if [[ "${2}" == '' ]]; then
ext_id='<unknown>'
else
ext_id="${2}"
fi
pr_err "Extension (id: %s) recipe for %s loaded from %s is invalid: %s. Please report that to the extension maintainer via help URL" \
"${2}" "${3}" "${1}" "${4}"
}
# Validates extension recipe file
#
# Args:
# $1 extension ID
# $2 platform ID
# $3 Path to the recipe file
# $x [removed?] Verify URLs [validate all URLs and post warnings if they're not valid, returning 2 if any are invalid]
# Exit: 0 - success; 1 - failure; 2 - soft-failure of URLs
mrp_validate_recipe_file()
{
pr_dbg "Validating extension index file \"%s\"" "${1}"
if ! brp_json_validate "${3}" 1; then # validate JSON *file*, not its format/semantic - it will be parsable
mrp_validate_ext_recipe_fail "${3}" "${1}" "${2}" 'recipe JSON file is unparsable'
return 1
fi
if [[ "$(brp_json_has_field "${3}" 'files')" -ne '1' ]]; then
mrp_validate_ext_recipe_fail "${3}" "${1}" "${2}" 'recipe file lacks files section'
return 1
fi
# we're not verifying the structure of files here as during the update it will do that attempting to download them
# todo: this should check if sections aren't empty but this is annoying in bash
if [[ "$(brp_json_has_field "${3}" 'kmods')" -ne '1' ]] && [[ "$(brp_json_has_field "${3}" 'scripts')" -ne '1' ]]; then
mrp_validate_ext_recipe_fail "${3}" "${1}" "${2}" 'recipe file does not define kmods nor scripts'
return 1
fi
local -A scripts_kv
brp_read_kv_to_array "${3}" "scripts" scripts_kv # we don't care about the order read here
for scr_name in "${!scripts_kv[@]}"; do
case "${scr_name}" in
on_boot)
;;
on_os_load)
;;
check_kmod)
if [[ "$(brp_json_has_field "${3}" 'kmods')" -ne '1' ]]; then
mrp_validate_ext_recipe_fail "${3}" "${1}" "${2}" 'recipe contains check_kmod script without any kmods defined'
return 1
fi
;;
*) pr_warn "Extension (id: %s) recipe for %s loaded from %s looks suspicious. It defines unknown script type %s" \
"${1}" "${2}" "${3}" "${scr_name}"
;;
esac
done
return 0
}
# Args: $1 URL
# Return: path to a temp file on exit=0
# Exit: 0 on exit, any other positive number indicates error
mrp_fetch_new_ext_index()
{
readonly URL="${1}"
local MRP_TMP_IDX="${RPT_EXTS_DIR}/_new_ext_index.tmp_json"
rm "${MRP_TMP_IDX}" &> /dev/null
if [ ${URL::1} == "#" ]; then
pr_warn "Index file copied locally from %s to %s" "${URL}" "${MRP_TMP_IDX}"
brp_cp_flat "${URL:1}" "${MRP_TMP_IDX}"
else
pr_dbg "Index is remote - getting from %s to %s" "${URL}" "${MRP_TMP_IDX}"
rpt_download_remote "${URL}" "${MRP_TMP_IDX}" 1
fi
echo "${MRP_TMP_IDX}"
}
# Args:
# $1 ID of the extension
# $2 platform code
# Exit:
# 0: success
# 1: error occurred (message will be printed)
mrp_fetch_new_ext_recipe()
{
pr_dbg "Fetching new recipe for extension %s and platform %s" "${1}" "${2}"
if ! mrp_validate_platform_id "${2}"; then # extension ID will be verified by mrp_get_existing_index_file
pr_err "Platform ID %s is not valid" "${2}"
return 1
fi
local index_file;
index_file=$(mrp_get_existing_index_file "${1}")
if [ $? -ne 0 ]; then
pr_err "Failed to load index file for extension %s - see errors above for details" "${1}"
return 1
fi
local recipe_url;
recipe_url=$(brp_json_get_field "${index_file}" "releases.${2}" 1)
if [[ $? -ne 0 ]] || [[ "${recipe_url}" == 'null' ]]; then
pr_err "The extension %s was found. However, the extension index has no recipe for %s platform. It may not be" "${1}" "${2}"
pr_err "supported on that platform, or author didn't updated it for that platform yet. You can try running"
pr_err "\"%s update\" to refresh indexes for all extensions manually. Below are the currently known information about" "${MRP_SRC_NAME}"
pr_err "the extension stored locally:"
mrp_show_ext_info "${1}"
return 1
fi
local mrp_tmp_rcp="${RPT_EXTS_DIR}/_ext_new_rcp.tmp_json"
rm "${mrp_tmp_rcp}" &> /dev/null
rpt_download_remote "${recipe_url}" "${mrp_tmp_rcp}" 1
brp_json_validate "${mrp_tmp_rcp}" # validate JSON *file*, not its format/semantic
echo "${mrp_tmp_rcp}"
}
# Gets recipe file for an extension (if exists)
# The file path returned is guaranteed to be a valid recipe file
#
# Args:
# $1 ID of the extension
# $2 Platform code
# Return: file path (if exit=0)
# Exit:
# 0 - success
# 1 - invalid values passed (syntax error)
# 2 - recipe not found
# 3 - recipe semantic validation failure (id, json struct, json semantic, etc)
mrp_get_existing_recipe_file_path()
{
if ! mrp_validate_id "${1}"; then
return 1
fi
local RCP_FILE="${RPT_EXTS_DIR}/${1}/${2}/${2}.json"
if [ ! -r "${RCP_FILE}" ]; then
return 2
fi
if ! mrp_validate_recipe_file "${1}" "${2}" "${RCP_FILE}"; then
return 3
fi
echo "${RCP_FILE}"
return 0
}
# Args: $1 extension id [optional]
__action_info()
{
if [[ "$#" -gt 1 ]]; then
pr_crit "\"%s info\" expected 0 or 1 argument - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
if [[ "$#" -eq 1 ]]; then
mrp_show_ext_info "${1}"
return
fi
local -a extensions
mrp_get_all_extensions extensions
for ext_id in ${extensions[@]+"${extensions[@]}"}; do
mrp_show_ext_info "${ext_id}"
done
}
# Args: $1 URL to index
__action_add()
{
if [[ "$#" -ne 1 ]]; then
pr_crit "\"%s add\" expected 1 argument - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
pr_process "Adding new extension from %s" "${1}"
local tmp_idx_file;
tmp_idx_file=$(mrp_fetch_new_ext_index "${1}")
if [[ $? -ne 0 ]]; then
pr_crit "Failed to add \"%s\" as an extension:\n\n%s\n", "${1}" "${tmp_idx_file}"
fi
if ! mrp_validate_index_file "${tmp_idx_file}" "${1}"; then
pr_crit "The index file for %s extension is invalid - please report that to the extension maintainer" "${1}"
fi
ext_id=$(brp_json_get_field "${tmp_idx_file}" 'id')
if [[ $? -ne 0 ]]; then
pr_crit "Failed to get ID from extension index file downloaded from \"%s\":\n\n%s\n", "${1}" "${ext_id}"
fi
# before we even validate new file we need to check if a given extension exists maybe
local IDX_FILE="${RPT_EXTS_DIR}/${ext_id}/${ext_id}.json"
if [[ -e "${IDX_FILE}" ]]; then
pr_crit "Extension is already added (index exists at %s). For more info use \"%s info %s\"" \
"${IDX_FILE}" "${MRP_SRC_NAME}" "${ext_id}"
fi
local THIS_EXT_DIR="${RPT_EXTS_DIR}/${ext_id}"
if [[ -e "${THIS_EXT_DIR}" ]]; then
pr_crit "Extension %s is damaged. Its index does NOT exists at %s but its folder DOES at %s" \
"${ext_id}" "${IDX_FILE}" "${THIS_EXT_DIR}"
fi
brp_mkdir "${THIS_EXT_DIR}"
brp_cp_flat "${tmp_idx_file}" "${IDX_FILE}"
"${RM_PATH}" "${tmp_idx_file}" || pr_warn "Failed to remove temp file %s" "${tmp_idx_file}"
pr_process_ok
mrp_show_ext_info "${ext_id}"
}
# Args: $1 id of the extension
__action_remove()
{
if [[ "$#" -ne 1 ]]; then
pr_crit "\"%s remove\" expected 1 argument - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
local idx_file;
idx_file="$(mrp_get_existing_index_file "${1}")" # id will be validated here [to prevent touching random files]
if [[ $? -ne 0 ]]; then
pr_crit "Extension cannot be removed due to previous errors"
fi
if ! "${RM_PATH}" -rf "${RPT_EXTS_DIR}/${1}"; then
pr_crit "Failed to remove %s extension directory" "${1}"
fi
pr_info "Extension %s has been removed" "${1}"
}
# Args: $1 extension ID | $2 URL to index
__action_force_add()
{
if [[ "$#" -ne 2 ]]; then
pr_crit "\"%s force_add\" expected 2 arguments - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
local index_file;
index_file=$(mrp_get_existing_index_file "${1}" 1)
local index_out=$?
## 0 - success
## 1 - parameters passed are invalid
## 2 - file not found
## 3 - semantic validation failure (json struct, json semantic, etc)
case $index_out in
0) # index was found & loaded correctly
pr_dbg "Extension %s is installed, checking URL" "${1}"
local index_url=$(brp_json_get_field "${index_file}" "url")
if [[ "${index_url}" != "${2}" ]]; then
pr_info "Reinstalling extension %s as its index location changed from %s to %s" "${1}" "${index_url}" "${2}"
__action_remove "${1}"
__action_add "${2}"
else
pr_info "Extension %s is already installed from %s" "${1}" "${index_url}"
fi
;;
1)
pr_crit "Extension ID \"%s\" is invalid - see above for details" "${1}"
;;
2) # not found/not installed
__action_add "${2}"
;;
3) # damaged
pr_warn "Extension %s is damaged - reinstalling from %s"
__action_remove "${1}"
__action_add "${2}"
;;
*)
pr_crit "Unknown error #%d occurred while loading extension %s index. Please report this as a bug." \
"${index_out}" "${1}"
esac
}
# Args: $1 list of extensions [optional, if not specified it will cleanup all]
__action_cleanup()
{
if [[ "$#" -gt 1 ]]; then
pr_crit "\"%s cleanup\" expected 0 or 1 argument - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
local -a extensions
if [[ -z "${1+0}" ]]; then # no extensions list passed - use all
pr_dbg "No extensions list passed - getting all"
mrp_get_all_extensions extensions
else # passed list of extensions - split them (we don't need to verify if they exist - we will try to read them anyway)
pr_dbg "Extensions list passed - splitting"
rpt_text_to_array ',' "${2}" extensions
fi
local failed=0
for ext_id in ${extensions[@]+"${extensions[@]}"}; do
pr_process "Removing all platform files for %s extension" "${ext_id}"
"${RM_PATH}" -rf "${RPT_EXTS_DIR}/${ext_id}/*" || pr_crit "Failed to cleanup extension %s" "${ext_id}"
pr_process_ok
done
}
# Called to error-out from recipe fill-in which failed
#
# Args: $1 platform dir to clean | $2 extension id | $3 platform id | $4 error
mrp_fill_recipe_fail()
{
pr_err "Recipe file for extension %s platform %s is logically invalid - %s. You SHOULD contact the extension packer to report that (see below)" \
"${2}" "${3}" "${4}"
mrp_show_ext_info "${ext_id}"
pr_dbg "Cleaning up %s" "${1}"
"${RM_PATH}" -rf "${1}"
if [[ $? -ne 0 ]]; then
pr_crit "Failed to delete %s after failed recipe fill attempt - please delete it manually"
fi
}
# Takes a new recipe, nukes old data for given ext+platform and downloads everything from the recipe
# It's assumed that the recipe passed here is semantically valid and matches the platform/extension. This function will
# always leave the directory for ext+platform in a valid state (or nuke it)
#
# Args: $1 extension id | $2 platform id | $3 path to recipe file to use [probably freshly downloaded]
mrp_fill_recipe()
{
local ext_id="${1}"
local platform_id="${2}"
local tmp_rcp_file="${3}"
local platform_rcp_file;
local platform_dir;
pr_info "Filling-in newly downloaded recipe for extension %s platform %s" "${ext_id}" "${platform_id}"
# prepare new directory for the extension+platform combo
platform_dir="${RPT_EXTS_DIR}/${ext_id}/${platform_id}"
pr_dbg "Platform dir for extension %s platform %s is %s" "${ext_id}" "${platform_id}" "${platform_dir}"
if [[ -d "${platform_dir}" ]]; then # we don't really care if the recipe is old or broken - if it's there nuke it
pr_dbg "Removing old platform dir %s" "${platform_dir}"
"${RM_PATH}" -rf "${RPT_EXTS_DIR}/${ext_id}/${platform_id}/"
if [[ $? -ne 0 ]]; then
pr_crit "Failed to delete %s while preparing to update extension %s for %s platform - try deleting it manually?" \
"${platform_dir}" "${ext_id}" "${platform_id}"
fi
fi
pr_dbg "Creating new platform dir %s" "${platform_dir}"
brp_mkdir "${platform_dir}"
platform_rcp_file="${platform_dir}/${platform_id}.json"
pr_dbg "Copying recipe %s to its permanent place in %s" "${tmp_rcp_file}" "${platform_rcp_file}"
brp_cp_flat "${tmp_rcp_file}" "${platform_rcp_file}"
local -a file_idxs;
local -A file_meta;
local platform_fpatch;
brp_json_get_keys "${platform_rcp_file}" 'files' file_idxs
for file_idx in "${file_idxs[@]}"; do
pr_dbg "Processing file entry #%d" "${file_idx}"
brp_read_kv_to_array "${platform_rcp_file}" "files[${file_idx}]" file_meta # todo: move that to recipe validation
for fm_key in name url sha256 packed; do #todo here also check the file name in recipe so there are no rogue chars
if [[ -z "${file_meta[${fm_key}]+0}" ]]; then
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "no [${fm_key}] key for file at idx #${file_idx}"
return 1
fi
done
platform_fpatch="${platform_dir}/${file_meta[name]}"
if [[ -e "${platform_fpatch}" ]]; then # this can happen if two file records define the same name OR previous packed archive had that file
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "file ${file_meta[name]} already exists while processing idx #${file_idx} (duplicated name?)"
return 1
fi
rpt_download_remote "${file_meta[url]}" "${platform_fpatch}" 0
if [[ $? -ne 0 ]]; then
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "failed to download file ${file_meta[url]}"
return 1
fi
brp_verify_file_sha256 "${platform_fpatch}" "${file_meta[sha256]}" 1
if [[ $? -ne 0 ]]; then
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "failed to verify file ${file_meta[url]}"
return 1
fi
if [[ "${file_meta[packed]}" == 'true' ]]; then
pr_dbg "File %s is marked as a packed archive - unpacking" "${platform_fpatch}"
brp_unpack_tar_flat "${platform_fpatch}" "${platform_dir}/" 0
if [ $? -ne 0 ]; then
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "file download ${file_meta[url]} cannot be unpacked"
return 1
fi
# this is critical as we don't want the archive accidentally landing in the final image
"${RM_PATH}" "${platform_fpatch}" || pr_crit "Failed to delete archive %s"
fi
done
# at this point we've got all files - now we need to check that all kernel modules & scripts defined are actually there
local -A files_kv;
if [[ "$(brp_json_has_field "${platform_rcp_file}" 'kmods')" -eq 1 ]]; then # not all extensions must have *.ko
pr_dbg "Extension has kernel modules - checking"
brp_read_kv_to_array "${platform_rcp_file}" "kmods" files_kv
for ko_name in "${!files_kv[@]}"; do
platform_fpatch="${platform_dir}/${ko_name}"
pr_dbg "Checking if file exists at %s" "${platform_fpatch}"
if [[ ! -f "${platform_fpatch}" ]]; then # it HAS TO BE a normal file, it's deliberate here
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "recipe defines kmod ${ko_name} *FILE* which doesn't exist after all files entries are processed"
return 1
fi
done
fi
if [[ "$(brp_json_has_field "${platform_rcp_file}" 'scripts')" -eq 1 ]]; then # not all extensions must have scripts
pr_dbg "Extension has scripts - checking"
brp_read_kv_to_array "${platform_rcp_file}" "scripts" files_kv
for script_action in "${!files_kv[@]}"; do
platform_fpatch="${platform_dir}/${files_kv[$script_action]}"
pr_dbg "Checking if file for action %s exists at %s" "${script_action}" "${platform_fpatch}"
if [[ ! -f "${platform_fpatch}" ]]; then # it HAS TO BE a normal file, it's deliberate here
mrp_fill_recipe_fail "${platform_dir}" "${ext_id}" "${platform_id}" "recipe defines ${files_kv[$script_action]} *FILE* for ${script_action} script action which doesn't exist after all files entries are processed"
return 1
fi
done
fi
pr_info "Successfully processed recipe for extension %s platform %s" "${ext_id}" "${platform_id}"
}
# Args: $1 list of extensions [optional, if not specified it will update all]
__action_update()
{
if [[ "$#" -gt 1 ]]; then
pr_crit "\"%s update\" expected 0 or 1 argument - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
local -a extensions
if [[ -z "${1+0}" ]]; then # no extensions list passed - use all
pr_dbg "No extensions list passed - getting all"
mrp_get_all_extensions extensions
else # passed list of extensions - split them (we don't need to verify if they exist - we will try to read them anyway)
pr_dbg "Extensions list passed - splitting"
rpt_text_to_array ',' "${1}" extensions
fi
local index_url
local cur_index_file
local cur_index_sha256
local new_index_file
local new_index_sha256
local failed=0
for ext_id in ${extensions[@]+"${extensions[@]}"}; do
pr_process "Updating %s extension" "${ext_id}"
cur_index_file=$(mrp_get_existing_index_file "${ext_id}")
if [ $? -ne 0 ]; then
pr_err "Failed to load index file for extension %s - see errors above for details" "${ext_id}"
return 1
fi
index_url=$(brp_json_get_field "${cur_index_file}" 'url')
new_index_file=$(mrp_fetch_new_ext_index "${index_url}")
if [[ $? -ne 0 ]]; then # so we cannot get new index
pr_err "Failed to download index file for %s extension from %s" "${ext_id}" "${index_url}"
((failed=failed+1))
continue
fi
# now we know we have the new [valid] recipe file (since previous if allowed us to get here)
pr_dbg "Got new index for %s from %s to %s" "${ext_id}" "${index_url}" "${new_index_file}"
cur_index_sha256=$(rpt_get_file_sha256 "${cur_index_file}")
if [[ $? -ne 0 ]]; then
pr_crit "Failed to hash old index file %s for comparison:\n\n%s" "${cur_index_file}" "${cur_index_sha256}"
fi
new_index_sha256=$(rpt_get_file_sha256 "${new_index_file}")
if [[ $? -ne 0 ]]; then
pr_crit "Failed to hash new index file %s for comparison:\n\n%s" "${new_index_file}" "${new_index_sha256}"
fi
pr_dbg "Current idx SHA256=%s vs new SHA256=%s" "${cur_index_sha256}" "${new_index_sha256}"
if [[ "${cur_index_sha256}" == "${new_index_sha256}" ]]; then
pr_info "Extension %s index is already up to date" "${ext_id}"
"${RM_PATH}" "${new_index_file}" || pr_warn "Failed to remove temp file %s" "${new_index_file}"
continue
fi
# todo: for now we can invalidate all platforms but properly we shouldn't invalidate if the recipe URLs are the same
# However, this is tricky as we need to compare list of platforms as well... it's VERY ANNOYING to do it in bash
"${RM_PATH}" -rf "${RPT_EXTS_DIR}/${ext_id}/*/"
"${MV_PATH}" "${new_index_file}" "${cur_index_file}" || pr_crit "Failed to install new index to %s" "${cur_index_file}"
done
if [[ "${failed}" -eq 0 ]]; then
pr_process_ok
else
pr_err "%d extensions failed to update" "${failed}"
fi
}
# Args: $1 platform id | $2 extensions ID list [optional]
__action__update_platform_exts()
{
if [[ "$#" -lt 1 ]] || [[ "$#" -gt 2 ]]; then
pr_crit "\"%s _update_platform_exts\" expected 1-2 arguments - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
pr_process "Updating %s platforms extensions" "${1}"
local platform_id="${1}"
if ! mrp_validate_platform_id "${platform_id}"; then
pr_err "Platform ID %s is not valid" "${platform_id}"
return 1
fi
local -a extensions
if [[ -z "${2+0}" ]]; then # no extensions list passed - use all
pr_dbg "No extensions list passed - getting all"
mrp_get_all_extensions extensions
else # passed list of extensions - split them (we don't need to verify if they exist - we will try to read them anyway)
pr_dbg "Extensions list passed - splitting"
rpt_text_to_array ',' "${2}" extensions
fi
local cur_recipe_file;
local cur_recipe_sha256;
local cur_recipe_result;
local new_recipe_file;
local new_recipe_sha256;
local platform_dir;
local hard_fail=0;
for ext_id in ${extensions[@]+"${extensions[@]}"}; do
pr_dbg "Processing extension %s for %s platform" "${ext_id}" "${platform_id}"
if ! mrp_has_extension "${ext_id}"; then
pr_crit "Extension \"%s\" is not added/installed - did you misspell the name or forgot to do \"%s add <URL>\" first?" \
"${ext_id}" "${MRP_SRC_NAME}"
fi
cur_recipe_file=$(mrp_get_existing_recipe_file_path "${ext_id}" "${platform_id}")
cur_recipe_result=$?
if [[ "${cur_recipe_result}" -eq 1 ]]; then
pr_crit "<BUG> Failed to read current recipe failure due to call error" # this is some bug in validation
else
pr_dbg "Attempted to get existing recipe - call resulted in code #%d" "${cur_recipe_result}"
fi
#todo: check if ok & recently checked and skip over the whole checking below (to not re-download receipt all the time)
new_recipe_file=$(mrp_fetch_new_ext_recipe "${ext_id}" "${platform_id}")
if [[ $? -ne 0 ]]; then # so we cannot get the recipe
case "${cur_recipe_result}" in
0) pr_warn "Failed to update recipe for %s extension for platform %s. The script will try to continue with the old" "${ext_id}" "${platform_id}"
pr_warn "recipe. If the old recipe still works at worst you will miss on an update. Contact the packer of the"
pr_warn "extension if this problem persists (displayed below)"
hard_fail=0
;;
2) pr_err "Failed to update recipe for %s extension for platform %s. The script will terminate as you do not" "${ext_id}" "${platform_id}"
pr_err "have previously downloaded recipe which can be used if download fails. Try again later. If problem"
pr_err "persists contact the extension packer for support (displayed below)"
hard_fail=1
;;
3) pr_err "Failed to update recipe for %s extension for platform %s. The script will terminate as your existing" "${ext_id}" "${platform_id}"
pr_err "recipe is damaged and cannot be used if download fails. Try again later. If problem persists"
pr_err "contact the extension packer for support (displayed below)"
hard_fail=1
;;
*) pr_crit "Unhandled cur_recipe_result exception #%d" "${cur_recipe_result}"
;;
esac
mrp_show_ext_info "${ext_id}"
if [[ "${hard_fail}" -eq 1 ]]; then
pr_crit "Cannot continue due to previous errors (see above)"
fi
continue # if there's no new recipe we cannot download any files or anything - we assume offline operation
fi
# now we know we have the new [valid] recipe file (since previous if allowed us to get here)
pr_dbg "Got new recipe to %s" "${new_recipe_file}"
if [[ "${cur_recipe_result}" -eq 0 ]]; then # we have the current one so we can try to check if it's the same
pr_dbg "Both new (%s) and old (%s) recipes exist - comparing" "${new_recipe_file}" "${cur_recipe_file}"
cur_recipe_sha256=$(rpt_get_file_sha256 "${cur_recipe_file}")
if [[ $? -ne 0 ]]; then
pr_crit "Failed to hash old recipe file %s for comparison:\n\n%s" "${cur_recipe_file}" "${cur_recipe_sha256}"
fi
new_recipe_sha256=$(rpt_get_file_sha256 "${new_recipe_file}")
if [[ $? -ne 0 ]]; then
pr_crit "Failed to hash new recipe file %s for comparison:\n\n%s" "${new_recipe_file}" "${new_recipe_sha256}"
fi
if [[ "${cur_recipe_sha256}" == "${new_recipe_sha256}" ]]; then
pr_info "Extension %s for %s platform is already up to date" "${ext_id}" "${platform_id}"
"${RM_PATH}" "${new_recipe_file}" || pr_warn "Failed to remove temp file %s" "${new_recipe_file}"
continue
fi
fi
pr_dbg "Previous recipe is not usable (outdated, broken, or missing) - processing newly downloaded one"
# now we know know that either ext is out of date or something is wrong with the current recipe (see previous if)
# either way we need to cleanup old one and simply get new one
# but just before we do that let's verify new recipe - if there's a new one and we confirmed it's different (or the
# old one is not found/broken) it should explode and user should not be able to complete update
if ! mrp_validate_recipe_file "${ext_id}" "${platform_id}" "${new_recipe_file}"; then
pr_err "Failed to update recipe for %s extension for platform %s. The script will terminate as the new recipe is" "${ext_id}"
pr_err "available but it's invalid. Try again later. If problem persists contact the extension packer for support"
pr_err "(displayed below)"
mrp_show_ext_info "${ext_id}"
pr_crit "Cannot continue due to previous errors (see above)"
fi
mrp_fill_recipe "${ext_id}" "${platform_id}" "${new_recipe_file}"
"${RM_PATH}" "${new_recipe_file}" || pr_warn "Failed to remove temp file %s" "${new_recipe_file}"
done
pr_process_ok
}
__action__dump_exts()
{
if [[ "$#" -lt 2 ]] || [[ "$#" -gt 3 ]]; then
pr_crit "\"%s _dump\" expected 2-3 arguments - got %d. See \"%s help\" for details" "${MRP_SRC_NAME}" "${#}" "${MRP_SRC_NAME}"
fi
local platform_id="${1}"
local dump_dir="${2}"
pr_process "Dumping %s platform extensions to %s" "${platform_id}" "${dump_dir}"
if ! mrp_validate_platform_id "${platform_id}"; then
pr_err "Platform ID %s is not valid" "${platform_id}"
return 1
fi
if [ ! -d "${dump_dir}" ] || [[ ! -z "$(${LS_PATH} -A "${dump_dir}")" ]]; then
pr_err "%s is not an **existing and empty** directory" "${dump_dir}"
return 1
fi
local -a extensions
if [[ -z "${3+0}" ]]; then # no extensions list passed - use all
pr_dbg "No extensions list passed - getting all"
mrp_get_all_extensions extensions
else # passed list of extensions - split them (we don't need to verify if they exist - we will try to read them anyway)
pr_dbg "Extensions list passed - splitting"
rpt_text_to_array ',' "${3}" extensions
fi
local platform_dir;
local dump_ext_di;
local platform_rcp_file;
local target_extensions; # list of extensions (space-separated) in order of how they should be loaded by the loader
local -A target_vars; # dump of variables for the script dumped to the final image to load everything in order
local -a files_keys;
local -A files_kv;
local ext_counter=-1;
local kmod_counter;
for ext_id in ${extensions[@]+"${extensions[@]}"}; do
((ext_counter++))
platform_dir="${RPT_EXTS_DIR}/${ext_id}/${platform_id}"
dump_ext_di="${dump_dir}/${ext_id}"
pr_dbg "Dumping platform %s extension %s from %s to %s" "${platform_id}" "${ext_id}" "${platform_dir}" "${dump_ext_di}"
platform_rcp_file=$(mrp_get_existing_recipe_file_path "${ext_id}" "${platform_id}")
if [[ $? -ne 0 ]]; then
pr_crit "Failed to dump extension %s for platform %s as its recipe file cannot be retrieved. Isn't the extension misspelled or not supported on that platform?" \
"${ext_id}" "${platform_id}"
fi
brp_cp_flat "${platform_dir}" "${dump_ext_di}" # theoretically this will suffice, but we can cleanup a bit
"${RM_PATH}" "${dump_ext_di}/${platform_id}.json" || true #this may safely fail (it shouldn't thou)
"${FIND_PATH}" "${dump_ext_di}" -type d -empty -delete # delete all empty dirs which may resulted from unpacking
# Handle kernel extensions (if any)
target_extensions+="${ext_id} " # POSIX shells don't care about leading/trailing whitespaces in IFS
if [[ "$(brp_json_has_field "${platform_rcp_file}" 'kmods')" -eq 1 ]]; then # not all extensions must have *.ko
pr_dbg "Extension has kernel modules - dumping"
brp_read_ordered_kv "${platform_rcp_file}" "kmods" files_keys files_kv
kmod_counter=0
for ko_name in ${files_keys[@]+"${files_keys[@]}"}; do
pr_dbg "Adding %s kmod (args: %s) from %s extension" "${ko_name}" "${files_kv[${ko_name}]}" "${ext_id}"
target_vars["EXT_${ext_counter}_kmod_files"]+="${ko_name} "
if [[ ! -z "${files_kv[${ko_name}]}" ]]; then
target_vars["EXT_${ext_counter}_kmod_${kmod_counter}_args"]="${files_kv[${ko_name}]}"
fi
((kmod_counter++))
done
fi
# Handle scripts (if any)
if [[ "$(brp_json_has_field "${platform_rcp_file}" 'scripts')" -eq 1 ]]; then # not all extensions must have scripts
pr_dbg "Extension has scripts - dumping"
brp_read_kv_to_array "${platform_rcp_file}" "scripts" files_kv # we don't care about the order read here
for script_action in "${!files_kv[@]}"; do
pr_dbg "Adding %s script for action %s from %s extension" \
"${script_action}" "${files_kv[${script_action}]}" "${ext_id}"
target_vars["EXT_${ext_counter}_scripts_${script_action}"]="${files_kv[${script_action}]}"
done
fi
done
# Copy bootstrap files
local target_exec_scr_path="${dump_dir}/exec.sh"
brp_cp_flat "include/loader-ext/readme.txt_" "${dump_dir}/__README__.txt"
brp_cp_flat "include/loader-ext/target_exec.sh_" "${target_exec_scr_path}"
rpt_make_executable "${target_exec_scr_path}"
# Fill-in bootstrap script
local ext_data='';
brp_replace_token_with_text "${target_exec_scr_path}" '@@@PLATFORM_ID@@@' "PLATFORM_ID=\"${platform_id}\""
brp_replace_token_with_text "${target_exec_scr_path}" '@@@EXTENSION_IDS@@@' "EXTENSION_IDS=\"${target_extensions}\""
for var_name in "${!target_vars[@]}"; do
ext_data+="${var_name}=\"${target_vars[${var_name}]}\""$'\n'
done
pr_dbg "Prepared EXT_DATA:\n%s" "${ext_data}"
brp_replace_token_with_text "${target_exec_scr_path}" '@@@EXT_DATA@@@' "${ext_data}"
}
__action_help ()
{
echo " RedPill Extensions Manager "
echo "=============================="
echo "Usage: ${MRP_SRC_NAME} <action> [arguments/options]"
echo "------------------------------"
echo "Actions intended for users:"
echo " info"
echo " Purpose: Gets information about an extension (or all of them)"
echo " Arguments:"
echo " [EXT_ID]: id of the extension; optional (if not specified it will list all)"
echo " Examples:"
echo " ${MRP_SRC_NAME} info # gets info about all installed"
echo " ${MRP_SRC_NAME} info thethorgroup.virtio # gets info about thethorgroup.virtio"