utils/pb-plugin: Add pb-plugin script
[petitboot] / utils / pb-plugin
1 #!/bin/sh
2
3 __dest=/
4 __pb_mount_dir=/var/petitboot/mnt/dev/
5 plugin_dev_meta=pb-plugin.conf
6 plugin_installed_meta_dir=/etc/preboot-plugins/
7
8 usage()
9 {
10         cat <<EOF
11 Usage: $0 <command>
12
13 Where <command> is one of:
14   install <FILE|URL>  - install plugin from FILE/URL
15   scan                - look for available plugins on attached devices
16   list                - list currently-installed plugins
17   create <DIR>        - create a new plugin archive from DIR
18 EOF
19 }
20
21 is_url()
22 {
23         local url tmp
24         url=$1
25         tmp=${url#*://}
26         [ "$url" != "$tmp" ]
27 }
28
29 download()
30 {
31         local url file proto
32         url=$1
33         file=$2
34         proto=${url%://*}
35
36         case "$proto" in
37         http)
38                 wget -O - "$url" > $file
39                 ;;
40         ftp)
41                 ncftpget -c "$url" > $file
42                 ;;
43         *)
44                 echo "error: Unsuported protocol $proto" >&2
45                 false
46         esac
47 }
48
49 plugin_info()
50 {
51         local title
52         if [ "$PLUGIN_VENDOR" ]
53         then
54                 title="$PLUGIN_VENDOR: $PLUGIN_NAME"
55         else
56                 title="$PLUGIN_NAME"
57         fi
58
59         echo "$title"
60         echo "  (version $PLUGIN_VERSION)"
61 }
62
63 do_install()
64 {
65         local url
66
67         url=$1
68
69         if [ -z "$url" ]
70         then
71                 echo "error: install requires a file/URL argument." >&2
72                 exit 1
73         fi
74
75         if [ ! -d "$__dest" ]
76         then
77                 echo "error: destination directory '$__dest' doesn't exist" >&2
78                 exit 1
79         fi
80
81         if [ ! -w "$__dest" ]
82         then
83                 echo "error: destination directory isn't writeable" >&2
84                 exit 1
85         fi
86
87         name=${url##*/}
88
89         if is_url "$url"
90         then
91                 file=$(mktemp)
92                 trap "rm '$file'" EXIT
93                 download "$url" "$file"
94                 if [ $? -ne 0 ]
95                 then
96                         echo "error: failed to download $url" >&2
97                         exit 1
98                 fi
99         else
100                 file=$url
101                 if [ ! -r "$file" ]
102                 then
103                         echo "error: $file doesn't exist or is not readable" >&2
104                         exit 1
105                 fi
106         fi
107
108         echo "File '$name' has the following sha256 checksum:"
109         echo
110         sha256sum "$file" | cut -f1 -d' '
111         echo
112         echo "Do you want to install into the pre-boot environment? (y/N)"
113         read resp
114
115         case $resp in
116         [yY]|[yY][eE][sS])
117                 ;;
118         *)
119                 echo "Cancelled"
120                 exit 0
121                 ;;
122         esac
123
124         gunzip -c "$file" | ( cd $__dest && cpio -i -d)
125
126         if [ $? -ne 0 ]
127         then
128                 echo "error: Failed to extract archive $url, exiting"
129                 exit 1
130         fi
131 }
132
133 do_scan()
134 {
135         local found
136         found=0
137         for mnt in $__pb_mount_dir/*
138         do
139                 dev=$(basename $mnt)
140                 metafile="$mnt/$plugin_dev_meta"
141                 [ -e "$metafile" ] || continue
142                 (
143                         . $metafile
144                         printf "Plugin found on %s:\n" $dev
145                         plugin_info
146                         printf "\n"
147                         printf "To install this plugin, run:\n"
148                         printf "  $0 install $mnt/$PLUGIN_FILE\n"
149                         printf "\n"
150                 )
151                 found=1
152         done
153
154         if [ "$found" = 0 ]
155         then
156                 echo "No plugins found"
157         fi
158 }
159
160 do_list()
161 {
162         local found
163         found=0
164         for meta in $plugin_installed_meta_dir/*
165         do
166                 [ -e "$meta" ] || continue
167                 [ $found = 0 ] && printf "Installed plugins:\n"
168                 found=1
169                 (
170                         . $meta
171                         plugin_info
172                         echo
173                 )
174         done
175
176         if [ "$found" = 0 ]
177         then
178                 echo "No plugins installed"
179         fi
180 }
181
182 guided_meta()
183 {
184         local vendorname vendorshortname
185         local pluginname pluginnhortname
186         local version date
187         local dir
188
189         dir=$1
190
191 cat <<EOF
192
193 Enter the vendor company / author name. This can contain spaces.
194 (eg. 'Example Corporation')
195 EOF
196         read vendorname
197 cat <<EOF
198
199 Enter the vendor shortname. This should be a single-word abbreviation, in all
200 lower-case. This is only used in internal filenames.
201
202 Typically, a stock-ticker name is used for this (eg 'exco')
203 EOF
204         read vendorshortname
205
206 cat <<EOF
207
208 Enter the descriptive plugin name. This can contain spaces, but should only be
209 a few words in length (eg 'RAID configuration utility')
210 EOF
211         read pluginname
212
213 cat <<EOF
214
215 Enter the plugin shortname. This should not contain spaces, but hyphens are
216 fine (eg 'raid-config'). This is only used in internal filnames.
217 EOF
218         read pluginshortname
219
220
221 cat <<EOF
222
223 Enter the plugin version. This should not contain spaces (eg 1.2):
224 EOF
225         read version
226
227         date=$(date +%Y-%m-%d)
228
229         mkdir -p $dir
230
231         cat <<EOF > $dir/$vendorshortname-$pluginshortname
232 PLUGIN_VENDOR='$vendorname'
233 PLUGIN_NAME='$pluginname'
234 PLUGIN_VERSION='$version'
235 PLUGIN_DATE='$date'
236 EOF
237
238 }
239
240 do_create()
241 {
242         local src found meta_dir_abs meta_file
243         src=$1
244
245         if [ -z "$src" ]
246         then
247                 echo "error: missing source directory" >&2
248                 usage
249                 exit 1
250         fi
251
252         if [ ! -d "$src" ]
253         then
254                 echo "error: source directory missing" >&2
255                 exit 1
256         fi
257
258         meta_dir_abs="$src/$plugin_installed_meta_dir"
259         found=0
260         for meta in $meta_dir_abs/*
261         do
262                 [ -e "$meta" ] || continue
263                 found=$(($found+1))
264                 meta_file=$meta
265         done
266
267         if [ $found = 0 ]
268         then
269                 echo "No plugin metadata file found. " \
270                         "Would you like to create one? (Y/n)"
271                 read resp
272                 case "$resp" in
273                 [nN]|[nN][oO])
274                         echo "Cancelled, exiting"
275                         exit 1
276                         ;;
277                 esac
278                 guided_meta $meta_dir_abs || exit
279                 meta_file=$meta_dir_abs/*
280         fi
281
282         if [ $found -gt 1 ]
283         then
284                 echo "error: Multiple metadata files found in $meta_dir_abs" >&2
285                 exit 1
286         fi
287
288         # Sanity check metadata file
289         (
290                 . $meta_file
291                 if [ ! -n "$PLUGIN_VENDOR" ]
292                 then
293                         echo "error: no PLUGIN_VENDOR defined in metadata" &>2
294                         exit 1
295                 fi
296                 if [ ! -n "$PLUGIN_NAME" ]
297                 then
298                         echo "error: no PLUGIN_NAME defined in metadata" &>2
299                         exit 1
300                 fi
301                 if [ ! -n "$PLUGIN_VERSION" ]
302                 then
303                         echo "error: no PLUGIN_VERSION defined in metadata" &>2
304                         exit 1
305                 fi
306                 if [ ! -n "$PLUGIN_DATE" ]
307                 then
308                         echo "error: no PLUGIN_DATE defined in metadata" &>2
309                         exit 1
310                 fi
311
312         ) || exit 1
313
314         outfile=pb-plugin.cpio.gz
315
316         (
317                 cd $src
318                 find -mindepth 1 | cpio -o -Hnewc -v
319         ) | gzip -c > pb-plugin.cpio.gz
320
321         cp $meta_file $plugin_dev_meta
322         echo "PLUGIN_FILE='$outfile'" >> $plugin_dev_meta
323
324         echo
325         echo "Plugin metadata:"
326         sed -e 's/^/  /' $meta_file
327         echo
328
329         echo "User-visible metadata:"
330
331         (
332                 . $meta_file
333                 plugin_info | sed -e 's/^/  /'
334         )
335
336         echo
337
338
339 cat <<EOF
340 Plugin created in:
341   $outfile
342   
343 Metadata in:
344   $plugin_dev_meta
345
346 If you rename $outfile (or distribute it in a non-root directory), then
347 also update the PLUGIN_FILE variable in $plugin_dev_meta.
348 EOF
349 }
350
351 test_http_download()
352 {
353         local tmp ref
354
355         tmp=$(mktemp -p $test_tmpdir)
356         ref=$(mktemp -p $test_tmpdir)
357
358         echo $RANDOM > $ref
359
360         wget()
361         {
362                 cat $ref
363         }
364
365         download http://example.com/test $tmp
366         cmp -s "$ref" "$tmp"
367 }
368
369 test_ftp_download()
370 {
371         local tmp ref
372
373         tmp=$(mktemp -p $test_tmpdir)
374         ref=$(mktemp -p $test_tmpdir)
375
376         echo $RANDOM > $ref
377
378         ncftpget()
379         {
380                 cat $ref
381         }
382
383         download ftp://example.com/test $tmp
384         cmp -s "$ref" "$tmp"
385 }
386
387 test_scan()
388 {
389         __pb_mount_dir="$test_tmpdir/mnt"
390         mnt_dir="$__pb_mount_dir/sda"
391         mkdir -p $mnt_dir
392         (
393                 echo "PLUGIN_NAME=test"
394                 echo "PLUGIN_VERSION=1"
395                 echo "PLUGIN_FILE=data/pb-plugin.cpio.gz"
396         ) > $mnt_dir/$plugin_dev_meta
397
398         do_scan | grep -q 'test 1'
399         rc=$?
400 }
401
402 test_empty_scan()
403 {
404         do_scan | grep -q "No plugins"
405 }
406
407 test_setup()
408 {
409         n=$(($n+1))
410
411         test_tmpdir="$tests_tmpdir/$n"
412         mkdir "$test_tmpdir"
413         __test_dest="$test_tmpdir/base"
414         mkdir "$__test_dest"
415         [ -d "$__test_dest" ] || exit 1
416         __dest=$__test_dest
417 }
418
419 test_teardown()
420 {
421         true
422 }
423
424 test_failed=0
425 do_test()
426 {
427         local tstr op
428
429         tstr="$@"
430         op=-eq
431
432         if [ "x$1" = "x!" ]
433         then
434                 op=-ne
435                 shift
436         fi
437
438         test_setup
439         ( $@ )
440         local rc=$?
441         test_teardown
442
443         if [ $rc $op 0 ]
444         then
445                 echo PASS: "$tstr"
446         else
447                 echo FAIL: "$tstr"
448                 test_failed=1
449                 false
450         fi
451 }
452
453 do_tests()
454 {
455         local tests_tmpdir n
456
457         tests_tmpdir=$(mktemp -d)
458         n=0
459
460         do_test ! is_url "/test"
461         do_test ! is_url "./test"
462         do_test ! is_url "../test"
463         do_test ! is_url "test"
464         do_test is_url "http://example.com/path"
465         do_test is_url "git+ssh://example.com/path"
466         do_test test_http_download
467         do_test test_ftp_download
468         do_test test_scan
469         do_test test_empty_scan
470
471         if [ $test_failed = 0 ]
472         then
473                 echo "$n tests passed"
474         else
475                 echo "Tests failed"
476                 false
477         fi
478         rm -rf "$tests_tmpdir"
479 }
480
481 case "$1" in
482 install)
483         shift
484         do_install $@
485         ;;
486 scan)
487         shift
488         do_scan $@
489         ;;
490 list)
491         shift
492         do_list $@
493         ;;
494 create)
495         shift
496         do_create $@
497         ;;
498 __test)
499         shift
500         do_tests $@
501         ;;
502 "")
503         echo "error: Missing command" >&2
504         usage
505         exit 1
506         ;;
507 *)
508         echo "Invalid command: $s" >&2
509         usage
510         exit 1
511 esac
512
513