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