Improve error handling.
[grml2usb.git] / grml2iso
1 #!/usr/bin/env bash
2 # Filename:      grml2iso
3 # Purpose:       create a multiboot grml ISO using grml2usb
4 # Authors:       Michael Prokop <mika@grml.org>,
5 #                Thorsten Glaser <tg@mirbsd.org>
6 # Bug-Reports:   see http://grml.org/bugs/
7 # License:       This file is licensed under the GPL v2 or any later version.
8 ################################################################################
9
10 # define function getfilesize before "set -e" {{{
11   if stat --help >/dev/null 2>&1; then
12     getfilesize='stat -c %s'        # GNU stat
13   else
14     getfilesize='stat -f %z'        # BSD stat
15   fi
16 # }}}
17
18 # adjust variables if necessary through environment {{{
19 # path to the grml2usb script you'd like to use
20   [ -n "$GRML2USB" ] || GRML2USB='grml2usb'
21 # work directory for creating the filesystem
22   [ -n "$TMPDIR" ]   && WRKDIR="${TMPDIR}/grml2iso.tmp"
23   [ -n "$WRKDIR" ]   || WRKDIR='/tmp/grml2iso.tmp'
24 # support mkisofs as well as genisoimage
25 if which xorriso >/dev/null 2>&1 ; then
26   MKISOFS='xorriso -as mkisofs'
27 elif which mkisofs >/dev/null 2>&1; then
28   MKISOFS='mkisofs'
29 elif which genisoimage >/dev/null 2>&1; then
30   MKISOFS='genisoimage'
31 else
32   echo >&2 "Error: neither mkisofs nor genisoimage available - can not create ISO."
33   exit 1
34 fi
35
36 if ! which isohybrid >/dev/null 2>&1 ; then
37   echo "Error: isohybrid executable not found (install syslinux?)." >&2
38   exit 1
39 fi
40 # }}}
41
42 # helper stuff {{{
43   set -e
44
45   usage() {
46     echo >&2 "Usage: $0 [OPTIONS] -o target.iso source1.iso [source2.iso ...]"
47     echo >&2 "
48 Options:
49      -b Boot Params      Additional boot parameters passed to grml2usb
50      -c Directory        Copy files from directory to generated ISO
51      -f                  Force overwrite of existing target.iso
52      -r BootParam        Remove specified boot params.
53                          Can be specified multiple times.
54      -p <grml2usb param> Add the specified parameter to the grml2usb
55                          commandline. For a list of valid parameters have
56                          a look at the grml2usb manpage.
57                          Can be specified multiple times.
58      -s URI              Generate a small ISO file which downloads the squashfs
59                          file from the specified URI. Please note that due to
60                          restrictions in the bootprocess only IPs are allowed.
61                          Supported protocols are: http and ftp
62      -t Directory        Directory that should be used for temporary files
63                          during build. Defaults to /tmp/grml2iso.tmp if unset.
64
65      Examples:
66      $0 -s http://192.168.23.42:8000/grml/ -o small.iso grml64_2010.12.iso
67
68      Will generate a file small.iso which tries to download the squashfs file from
69      http://192.168.23.42:8000/grml/ - the squashfs file is placed in the same
70      output directory as the ISO file.
71 "
72     [ -n "$1" ] && exit $1 || exit 1
73   }
74 # }}}
75
76 # command line handling {{{
77   [[ $# -gt 2 ]] || usage 1
78
79   ISOFILE=''
80   DIR=''
81   ADD_OPTS=''
82   FORCE=''
83   URI=''
84   typeset -a GRML2USB_OPTS
85   while getopts fb:c:o:r:p:s:t: name; do
86     case $name in
87       o)   ISOFILE="$OPTARG";;
88       b)   GRML2USB_OPTS+=(--bootoptions="$OPTARG");;
89       c)   DIR="$(readlink -f "$OPTARG")"; [ -n "$DIR" ] || { echo "Could not read $OPTARG - exiting" >&2 ; exit 1 ; } ;;
90       f)   FORCE='true';;
91       r)   GRML2USB_OPTS+=(--remove-bootoption="$OPTARG");;
92       p)   GRML2USB_OPTS+=("$OPTARG");;
93       s)   URI="$OPTARG";;
94       t)   WRKDIR="$(readlink -f "$OPTARG")";;
95       ?)   usage 2;;
96     esac
97   done
98
99   # test for specified URI
100   if [ -n "$URI" ] ; then
101     GRML2USB_OPTS+=(--bootoptions="fetch=$URI")
102   fi
103
104   if [ -n "$WRKDIR" ] ; then
105     GRML2USB_OPTS+=(--tmpdir="$WRKDIR")
106   fi
107
108 # make sure -o is specified
109   [ -n "$ISOFILE" ] || usage 1
110
111 # we don't to override any files by accident
112   if [ -e "$ISOFILE" -a ! -n "$FORCE" ]; then
113     echo "Error: target file $ISOFILE exists already." >&2
114     exit 1
115   fi
116
117   if [ ! -z "$DIR" -a ! -d "$DIR" ] ; then
118      echo "Error: specified parameter for -c is not a directory" >&2
119      exit 1
120   fi
121 # }}}
122
123 # we need root permissions for executing grml2usb {{{
124   if [[ $(id -u) != 0 ]]; then
125     echo >&2 "Error: please run $0 as root."
126     exit 1
127   fi
128 # }}}
129
130 # check for grml2usb {{{
131   if [ ! -x "$(which $GRML2USB)" ] && [ ! -x "$GRML2USB" ] ; then
132     echo "Error: Could not find grml2usb executable. Is /usr/sbin missing in PATH?" >&2
133     echo "Tip: run GRML2USB=/usr/sbin/grml2usb grml2iso ... as workaround" >&2
134     if [ -x "./$GRML2USB" ] ; then
135       echo >&2 "If you executed grml2iso from the grml2usb repository use"
136       echo >&2 "GRML2USB=./grml2usb $0 $*"
137     fi
138     exit 1
139   fi
140 # }}}
141
142 # variables {{{
143   ORIG_DIR="$(pwd)"
144
145 # normalise path
146   case $ISOFILE in
147     /*) ;;
148     *) ISOFILE=$ORIG_DIR/$ISOFILE ;;
149   esac
150 # }}}
151
152 # create necessary stuff under WRKDIR {{{
153   [ -d "$WRKDIR" ] && WRKDIR_EXISTED='true' || WRKDIR_EXISTED='false'
154   rm -rf "$WRKDIR/cddir" "$WRKDIR/grub_tmp"
155   mkdir -p "$WRKDIR/cddir"
156 # }}}}
157
158 # execute grml2usb with all ISOs you'd like to install {{{
159   # remove all parameters
160   shift $(($OPTIND - 1))
161
162   $GRML2USB "${GRML2USB_OPTS[@]}" "$@" "$WRKDIR/cddir"
163 # }}}
164
165 # move syslinux to isolinux {{{
166   mv "$WRKDIR"/cddir/boot/syslinux "$WRKDIR"/cddir/boot/isolinux
167   echo "menu label ^Isolinux prompt" > "$WRKDIR"/cddir/boot/isolinux/promptname.cfg
168   echo "include hd.cfg" >> "$WRKDIR"/cddir/boot/isolinux/grmlmain.cfg
169 # }}}
170
171 # change to $WRKDIR {{{
172   # make sure $WRKDIR is an absolute path, otherwise accessing files
173   # in it will fail later in the code path if user provided a
174   # relative directory
175   WRKDIR=$(realpath $WRKDIR)
176   cd "$WRKDIR/cddir"
177 # }}}
178
179 # efi boot {{{
180   # default, independent of UEFI support
181   BOOT_ARGS="-no-emul-boot -boot-load-size 4 -boot-info-table -b boot/isolinux/isolinux.bin -c boot/isolinux/boot.cat"
182
183   case "$MKISOFS" in
184     xorriso*)
185       echo "Using xorriso for ISO generation."
186       if ! dpkg --compare-versions $(dpkg-query -W -f='${Version}\n' xorriso 2>/dev/null) gt-nl 1.1.6-1 ; then
187         echo "Disabling (U)EFI boot support since xorriso version is not recent enough."
188       else
189         echo "xorriso with -eltorito-alt-boot support present"
190
191         if ! [ -r "${WRKDIR}/cddir/boot/efi.img" ] ; then
192           echo "File /boot/efi.img not found, not extending boot arguments for (U)EFI boot."
193         else
194           echo "/boot/efi.img found, extending boot arguments for (U)EFI boot."
195           BOOT_ARGS="$BOOT_ARGS -boot-info-table -eltorito-alt-boot -e boot/efi.img -no-emul-boot"
196         fi
197       fi
198       ;;
199   esac
200 # }}}
201
202 # adjust ISO for small output if necessary {{{
203   if [ -n "$URI" ] ; then
204      bootloader_files=$(find . -name "*.cfg" -type f)
205      bootloader_files+=" "
206      bootloader_files+=$(find . -name "*.lst" -type f)
207      output_dir=$(dirname "$ISOFILE")
208      for squashfs in $(find . -name *.squashfs) ; do
209         media_path="$(dirname "$squashfs")"
210         filename="$(basename "$squashfs")"
211         target="$output_dir/$filename"
212         if [ -f "$target" ] && [ ! -n "$FORCE" ] ; then
213            echo >&2 "Warning: $target already exists, and -force not specified, not overwriting"
214         else
215            mv $squashfs $target
216            OUTPUT_FILES+=("$target")
217         fi
218         sed -i -e "s#^\(^.*$media_path.*\)\($URI\)\(.*$\)#\1$URI/$filename\3#g" $bootloader_files
219
220    done
221   fi
222 # }}}
223
224 # copy specified directory to cd {{{
225   if [ -n "$DIR" ] ; then
226      echo >&2 "Copying ${DIR} to generated ISO"
227      for param in GRML_NAME VERSION RELEASENAME DATE SHORT_NAME \
228          VERSION BOOTID RELEASE_INFO ; do
229        EXCLUDE_PARAM="$EXCLUDE_PARAM --exclude **%${param}%**"
230      done
231      rsync -a ${DIR}/ $EXCLUDE_PARAM .
232   fi
233
234   # adjust files from overlay directory
235   for GRML_VERSION_FILE in $(find . -name grml-version) ; do
236     GRML_NAME=$(awk '{print $1}' "$GRML_VERSION_FILE")
237     VERSION=$(awk '{print $2}' "$GRML_VERSION_FILE")
238     RELEASENAME=$(sed 's/.*- \(.*\).*\[.*/\1/' "$GRML_VERSION_FILE")
239     DATE=$(sed 's/.*\[\(.*\)].*/\1/' "$GRML_VERSION_FILE")
240     SHORT_NAME="$(echo $GRML_NAME | tr -d ',./;\- ')"
241     RELEASE_INFO="$GRML_NAME $VERSION - $RELEASENAME"
242     BOOTID=$(cat conf/bootid.txt)
243
244     for param in GRML_NAME VERSION RELEASENAME DATE SHORT_NAME \
245         RELEASE_INFO BOOTID  ; do
246       value="$(eval echo '$'"$param")"
247
248       # copy parameterized files from the overlay directory
249       for file in $(find ${DIR} -name "*%$param%*") ; do
250         file=${file##$DIR/}
251         target_dir="$(dirname ${file})"
252         mkdir -p "$target_dir" || true
253         cp -r ${DIR}/${file} ./${target_dir}/"$(basename ${file/\%${param}\%/$value})"
254       done
255
256       # adjust config files
257       for file in ./boot/isolinux/*.cfg ./boot/isolinux/*.msg \
258         ./boot/grub/*.cfg ; do
259         sed -i "s/%$param%/$value/g" ${file} 2>/dev/null || true
260       done
261     done
262   done
263 # }}}
264
265 # generate the CD/DVD ISO {{{
266   $MKISOFS -V 'grml-multiboot' -l -r -J -no-pad $BOOT_ARGS \
267     -o "$ISOFILE" .
268 # }}}
269
270 # pad the output ISO to multiples of 256 KiB for partition table support {{{
271   siz=$($getfilesize "$ISOFILE")
272   cyls=$(($siz / 512 / 32 / 16 + 1))  # C=$cyls H=16 S=32
273   ofs=$(($cyls * 16 * 32 * 512 - 1))  # padding offset (size - 1)
274   dd if=/dev/zero bs=1 count=1 seek=$ofs of="$ISOFILE" 2>/dev/null
275 # }}}
276
277 # make ISO dd-able {{{
278   if isohybrid --help | grep -q -- --uefi ; then
279     echo "isohybrid version supports --uefi option, enabling"
280     ISOHYBRID_OPTIONS=--uefi
281  else
282     echo "isohybrid version does NOT support --uefi option, disabling"
283   fi
284
285   echo "Creating dd-able ISO using isohybrid"
286   isohybrid $ISOHYBRID_OPTIONS "$ISOFILE"
287 # }}}
288
289 # cleanup {{{
290   cd "$ORIG_DIR"
291   sync
292   rm -rf "$WRKDIR/cddir" "$WRKDIR/grub_tmp"
293   [[ $WRKDIR_EXISTED = 'false' ]] && rmdir "$WRKDIR"
294   echo "Generated $ISOFILE"
295   if [ -n "$URI" ] ; then
296      echo "
297 Information:
298 ==============
299 You requested to generate a small ISO image. Your generated
300 ISO image $ISOFILE does _not_ contain the squashfs files from
301 the source ISO images.
302
303 You have to provide the extracted squashfs files under $URI.
304
305 ISO image: $ISOFILE
306 Squashfs files: ${OUTPUT_FILES[@]}
307 URI: $URI
308 "
309   fi
310 # }}}
311
312 ## EOF #########################################################################
313 # vim:foldmethod=marker ts=2 ft=sh ai expandtab tw=80 sw=3