31406c59d0863bf77b9be29c1a673ac0c4fada54
[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 "Error: neither xorriso nor mkisofs nor genisoimage available - can not create ISO." >&2
33   exit 1
34 fi
35
36 if ! which isohybrid >/dev/null 2>&1 ; then
37   echo "Error: isohybrid executable not found (install syslinux/isolinux?)." >&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   UEFI_ENABLE=true
183
184   case "$MKISOFS" in
185     xorriso*)
186       echo "Using xorriso for ISO generation."
187       if ! dpkg --compare-versions $(dpkg-query -W -f='${Version}\n' xorriso 2>/dev/null) gt-nl 1.1.6-1 ; then
188         echo "Disabling (U)EFI boot support since xorriso version is not recent enough."
189       else
190         echo "xorriso with -eltorito-alt-boot support present"
191
192         if ! [ -r "${WRKDIR}/cddir/boot/efi.img" ] ; then
193           echo "Warning: File /boot/efi.img not found, not extending boot arguments for (U)EFI boot."
194           UEFI_ENABLE=false
195         else
196           echo "/boot/efi.img found, extending boot arguments for (U)EFI boot."
197           BOOT_ARGS="$BOOT_ARGS -boot-info-table -eltorito-alt-boot -e boot/efi.img -no-emul-boot"
198         fi
199       fi
200       ;;
201     *)
202       if ! [ -r "${WRKDIR}/cddir/boot/efi.img" ] ; then
203         echo "Warning: File /boot/efi.img not found."
204         UEFI_ENABLE=false
205       fi
206       ;;
207   esac
208 # }}}
209
210 # adjust ISO for small output if necessary {{{
211   if [ -n "$URI" ] ; then
212      bootloader_files=$(find . -name "*.cfg" -type f)
213      bootloader_files+=" "
214      bootloader_files+=$(find . -name "*.lst" -type f)
215      output_dir=$(dirname "$ISOFILE")
216      for squashfs in $(find . -name *.squashfs) ; do
217         media_path="$(dirname "$squashfs")"
218         filename="$(basename "$squashfs")"
219         target="$output_dir/$filename"
220         if [ -f "$target" ] && [ ! -n "$FORCE" ] ; then
221            echo >&2 "Warning: $target already exists, and -force not specified, not overwriting"
222         else
223            mv $squashfs $target
224            OUTPUT_FILES+=("$target")
225         fi
226         sed -i -e "s#^\(^.*$media_path.*\)\($URI\)\(.*$\)#\1$URI/$filename\3#g" $bootloader_files
227
228    done
229   fi
230 # }}}
231
232 # copy specified directory to cd {{{
233   if [ -n "$DIR" ] ; then
234      echo >&2 "Copying ${DIR} to generated ISO"
235      for param in GRML_NAME VERSION RELEASENAME DATE SHORT_NAME \
236          VERSION BOOTID RELEASE_INFO ; do
237        EXCLUDE_PARAM="$EXCLUDE_PARAM --exclude **%${param}%**"
238      done
239      rsync -a ${DIR}/ $EXCLUDE_PARAM .
240   fi
241
242   # adjust files from overlay directory
243   for GRML_VERSION_FILE in $(find . -name grml-version) ; do
244     GRML_NAME=$(awk '{print $1}' "$GRML_VERSION_FILE")
245     VERSION=$(awk '{print $2}' "$GRML_VERSION_FILE")
246     RELEASENAME=$(sed 's/.*- \(.*\).*\[.*/\1/' "$GRML_VERSION_FILE")
247     DATE=$(sed 's/.*\[\(.*\)].*/\1/' "$GRML_VERSION_FILE")
248     SHORT_NAME="$(echo $GRML_NAME | tr -d ',./;\- ')"
249     RELEASE_INFO="$GRML_NAME $VERSION - $RELEASENAME"
250     BOOTID=$(cat conf/bootid.txt)
251
252     for param in GRML_NAME VERSION RELEASENAME DATE SHORT_NAME \
253         RELEASE_INFO BOOTID  ; do
254       value="$(eval echo '$'"$param")"
255
256       # copy parameterized files from the overlay directory
257       for file in $(find ${DIR} -name "*%$param%*") ; do
258         file=${file##$DIR/}
259         target_dir="$(dirname ${file})"
260         mkdir -p "$target_dir" || true
261         cp -r ${DIR}/${file} ./${target_dir}/"$(basename ${file/\%${param}\%/$value})"
262       done
263
264       # adjust config files
265       for file in ./boot/isolinux/*.cfg ./boot/isolinux/*.msg \
266         ./boot/grub/*.cfg ; do
267         sed -i "s/%$param%/$value/g" ${file} 2>/dev/null || true
268       done
269     done
270   done
271 # }}}
272
273 # generate the CD/DVD ISO {{{
274   $MKISOFS -V 'grml-multiboot' -l -r -J -no-pad $BOOT_ARGS \
275     -o "$ISOFILE" .
276 # }}}
277
278 # pad the output ISO to multiples of 256 KiB for partition table support {{{
279   siz=$($getfilesize "$ISOFILE")
280   cyls=$(($siz / 512 / 32 / 16 + 1))  # C=$cyls H=16 S=32
281   ofs=$(($cyls * 16 * 32 * 512 - 1))  # padding offset (size - 1)
282   dd if=/dev/zero bs=1 count=1 seek=$ofs of="$ISOFILE" 2>/dev/null
283 # }}}
284
285 # make ISO dd-able {{{
286   if ! $UEFI_ENABLE ; then
287     echo "Skipping check for --uefi option in isohybrid since /boot/efi.img does not exist."
288   else
289     if ! isohybrid --help | grep -q -- --uefi ; then
290       echo "isohybrid version does NOT support --uefi option, disabling"
291     else
292       echo "isohybrid version supports --uefi option"
293       ISOHYBRID_OPTIONS=--uefi
294     fi
295
296     echo "Creating dd-able ISO using isohybrid"
297     isohybrid $ISOHYBRID_OPTIONS "$ISOFILE"
298   fi
299 # }}}
300
301 # cleanup {{{
302   cd "$ORIG_DIR"
303   sync
304   rm -rf "$WRKDIR/cddir" "$WRKDIR/grub_tmp"
305   [[ $WRKDIR_EXISTED = 'false' ]] && rmdir "$WRKDIR"
306   echo "Generated $ISOFILE"
307   if [ -n "$URI" ] ; then
308      echo "
309 Information:
310 ==============
311 You requested to generate a small ISO image. Your generated
312 ISO image $ISOFILE does _not_ contain the squashfs files from
313 the source ISO images.
314
315 You have to provide the extracted squashfs files under $URI.
316
317 ISO image: $ISOFILE
318 Squashfs files: ${OUTPUT_FILES[@]}
319 URI: $URI
320 "
321   fi
322 # }}}
323
324 ## EOF #########################################################################
325 # vim:foldmethod=marker ts=2 ft=sh ai expandtab tw=80 sw=2