+ added to branch.
[bootcd.git] / bootcustom.sh
1 #!/bin/bash
2
3 # purpose : create a node-specific boot image
4
5 # NOTE (see also bootcd/build.sh)
6 # If you run your own myplc instance, and you dont need to
7 # customize the bootcd, you might wish to use bootcd/build.sh
8 # with the -f option
9 # However cdcustom.sh might turn out useful if
10 # (*) you only have an iso or usb image and nothing else
11 # (*) or you want to generate several images in a single run
12 # (*) or you run myplc rpm, but need to customize the bootcd image,
13 #     because the myplc rpm does not come with the required sources
14
15 # See usage for full details
16
17 ######## Implementation notes
18 # (*) as of may 2007, this script supports both iso and usb images
19 #     this becomes a bit tricky sometimes
20 #     in particular the startup is only partially lazy..
21 #     most names are kept from former version for easier merge
22 # (*) in a former release (iso only) it was possible to perform faster by
23 # loopback-mounting the generic iso image
24 # Unfortunately mkisofs cannot graft a file that already exists on the
25 # original tree (so overlay.img cannot be overridden)
26 # to make things worse we cannot loopback-mount the cpio-gzipped
27 # overlay image either, so all this stuff is way more complicated
28 # than it used to be.
29 #
30 # (*) as of 2006 jun 28 we use a third image named custom.img for
31 # overriding files in bootcd.img, which let us use bootcd.img intact
32 # and thus notably speeds things up 
33 #
34 ######## Logic
35 # here is how we do this for ISO
36 # for efficiency, we do only once:
37 #   (*) mount the generic image
38 #   (*) copy it into a temp dir
39 #   (*) unzip/unarchive overlay image into another temp dir
40 #   (*) if required prepare a custom.img 
41 # then for each node, we
42 #   (*) insert plnode.txt at the right place if not a default iso
43 #   (*) rewrap a gzipped/cpio overlay.img, that we push onto the
44 #       copied iso tree
45 #   (*) rewrap this into an iso image
46
47 # for USB
48 # in principle it's simpler here, but for better code factorization we
49 # do the same startup thing, but slightly different
50 # only once:
51 #   (*) mount the generic image
52 #   (*) unzip/unarchive overlay image into another temp dir
53 # then for each node, we
54 #   (*) copy the generic image into the node-dep image
55 #   (*) loopback mount it (write enabled)
56 #   (*) insert plnode.txt at the right place, 
57 #   (*) rewrap a gzipped/cpio overlay.img, that we push onto the
58 #       mounted image
59 #   (*) unmount
60
61 set -e 
62 COMMANDSH=$(basename $0)
63 COMMAND=$(basename $0 .sh)
64 REVISION="$Id: bootcustom.sh,v 1.1 2007/08/08 20:22:11 soltesz Exp $"
65
66 function usage () {
67
68    echo "Usage: $COMMANDSH [-f] [ -c bootcd-dir] [generic-image|image-dir] node-config [.. node-configs]"
69    echo " Creates a node-specific boot (iso or usb) image"
70    echo "*Options"
71    echo -e " -f\r\t\tForces overwrite of output images"
72    echo -e " -c bootcd-dir\r\t\tis taken as the root of a set of custom bootcd files"
73    echo -e "\t\ti.e. the files under dir take precedence"
74    echo -e "\t\tover the ones in the generic bootcd"
75    echo -e "\t\tThis is available for ISO images only, and is experimental code"
76    echo -e "-C dir\r\t\tlike make -C, performs chdir prior to running anything"
77    echo -e "-O isoname\r\t\tWrite iso output image to isoname. Does not work with"
78    echo -e "\t\tmultiple node-config files."
79    echo "*Arguments"
80    echo -e " generic-image or image-dir\n\r\t\tThe generic image as downloaded from myplc"
81    echo -e "\t\ttypically from http://myplc.domain.org/download/"
82    echo -e "\t\tor, the already mounted or copied contents in a directory."
83    echo -e " node-config(s)\r\t\tnode config files (plnode.txt format)"
84    echo -e " default\r\t\tmentioned instead of a plnode.txt file, for generating"
85    echo -e "\t\ta node-independent iso image when -c is provided"
86    echo -e "\t\tThis is default behaviour when no node-config are provided"
87    echo "*Outputs"
88    echo " node-specific images are named after nodename[-bootcd-dir]"
89    echo " node-independant image is named after bootcd-dir"
90    echo " with extension .iso or .usb accordingly"
91    echo "*Examples"
92    echo "# $COMMANDSH /plc/data/var/www/html/download/Onelab-BootCD-4.0.usb node1.txt node2.txt"
93    echo "  Creates node1.usb and node2.usb that are self-contained USB images for both nodes"
94    echo "# $COMMANDSH -c onelab-bootcd /plc/data/var/www/html/download/onelab-BootCD-4.0.iso"
95    echo "  Creates onelab-bootcd.iso that has no plnode.txt embedded and that uses"
96    echo "  the hw init scripts located under onelab-bootcd/etc/rc.d/init.d/"
97    echo "*Version $REVISION"
98    exit 1
99 }
100
101 ### read config file in a subshell and echoes host_name
102 function host_name () {
103   export CONFIG="$1"; shift
104   ( . "$CONFIG" ; echo "${HOST_NAME}.${DOMAIN_NAME}" )
105 }
106
107 ### Globals
108 # we set this to iso or usb according to the generic-image provided
109 IMAGE_TYPE=
110 PLNODE_PATH=/usr/boot
111 PLNODE=plnode.txt
112 DEFAULT_TARGET=default
113 # defined on the command-line
114 CUSTOM_DIR=
115 ## arg-provided generic iso
116 ISO_GENERIC=
117 # node-dep conf file
118 NODE_CONFIG=
119 # resulting iso image and log
120 NODE_ISO=
121 NODE_LOG=
122 ## mount points and temps
123 ISO_MOUNT="/tmp/$COMMAND-$$-mount"
124 ISO_ROOT="/tmp/$COMMAND-$$-iso"
125 OVERLAY_ROOT="/tmp/$COMMAND-$$-overlay"
126 # node-dep cpio/gzip image
127 NODE_OVERLAY=
128
129 CPIO_OARGS="-oc --quiet"
130 CPIO_IARGS="-id --quiet"
131 CPIO_PARGS="-pdu --quiet"
132
133 # export DEBUG=true
134 # for enabling debug messages (set -x)
135
136 # export VERBOSE=true for enabling this
137 function verbose () {
138    if [ -n "$VERBOSE" ] ; then
139      echo "$@"
140    fi
141  }
142
143 function message () { echo -e "$COMMAND : $@" ; }
144 function message-n () { echo -en "$COMMAND : $@" ; }
145 function message-done () { echo Done ; }
146 function error () { echo -e "$COMMAND : ERROR $@ - exiting" ; exit 1 ;}
147
148 # lazy startup
149 STARTED_UP=
150 function startup () {
151
152    trap abort int hup quit err
153    set -e
154    
155    [[ -n "$DEBUG" ]] && set -x
156
157    # lazy : run only once
158    if [ -z "$STARTED_UP" ] ; then
159      message "lazy start up"
160
161      ### checking
162      [ ! -f "$ISO_GENERIC" ] && [ ! -d "$ISO_GENERIC" ] && error "Could not find template ISO image"
163      [ -d "$ISO_MOUNT" ] && error "$ISO_MOUNT already exists" 
164      [ -d "$ISO_ROOT" ] && [ ! -d "$ISO_GENERIC" ] && error "$ISO_ROOT already exists" 
165      [ -d "$OVERLAY_ROOT" ] && error "$OVERLAY_ROOT already exists"
166      
167      verbose "Creating temp dirs"
168      mkdir -p "$ISO_MOUNT" "$ISO_ROOT" "$OVERLAY_ROOT"
169
170      if [[ "$IMAGE_TYPE" = "usb" || "$IMAGE_TYPE" = "iso" ]] ; then
171          verbose "Mounting generic ISO $ISO_GENERIC under $ISO_MOUNT"
172          mount -o ro,loop "$ISO_GENERIC" "$ISO_MOUNT"
173      else
174          # the iso_generic name is the directory from which files are available
175          ISO_MOUNT=$ISO_GENERIC
176      fi
177      
178      if [[ "$IMAGE_TYPE" = "iso" || "$IMAGE_TYPE" = "dir" ]] ; then
179        ### ISO
180        ### DONT!! use tar for duplication
181        message "Duplicating ISO image in $ISO_ROOT"
182        (cd "$ISO_MOUNT" ; find . | cpio $CPIO_PARGS "$ISO_ROOT" )
183      
184        if [ -n "$CUSTOM_DIR" ] ; then
185          [ -d "$CUSTOM_DIR" ] || error "Directory $CUSTOM_DIR not found"
186          prepare_custom_image
187        fi
188      fi
189      ### USB specifics are done unconditionnally, see below
190      
191      message "Extracting generic overlay image in $OVERLAY_ROOT"
192      gzip -d -c "$ISO_MOUNT/overlay.img" | ( cd "$OVERLAY_ROOT" ; cpio $CPIO_IARGS )
193      
194      STARTED_UP=true
195    fi
196
197    # for USB: do this for every node
198    if [ "$IMAGE_TYPE" = "usb" ] ; then
199      message "Duplicating $ISO_GENERIC into $NODE_ISO"
200      cp "$ISO_GENERIC" "$NODE_ISO"
201      message "Mounting write-enabled"
202      mount -o loop "$NODE_ISO" "$ISO_ROOT"
203    fi
204    
205 }   
206
207 function prepare_custom_image () {
208
209    # Cleaning any sequel
210    rm -f custom.img
211    [ -f custom.img ] && error "Could not cleanup custom.img"
212    
213    message "WARNING : You are creating *custom* boot CDs"
214
215    message-n "Creating $ISO_ROOT/custom.img"
216    (cd $"CUSTOM_DIR" ; find . | cpio $CPIO_OARGS) | gzip -9 > "$ISO_ROOT"/custom.img
217    message-done
218    
219 }
220
221 function node_cleanup () {
222    verbose "Cleaning node-dependent cpio image"
223    rm -rf "$NODE_OVERLAY"
224   
225 }
226
227 function cleanup () {
228
229    echo "$COMMAND : cleaning up"
230    [[ -n "$DEBUG" ]] && set -x
231
232    verbose "Cleaning overlay image"
233    rm -rf "$OVERLAY_ROOT"
234    verbose "Cleaning ISO image"
235    rm -rf "$ISO_ROOT"
236    verbose "Cleaning node-dep overlay image"
237    rm -f "$NODE_OVERLAY"
238    if [[ "$IMAGE_TYPE" = "iso" || "$IMAGE_TYPE" = "usb" ]]; then
239        verbose "Unmounting $ISO_MOUNT"
240        umount "$ISO_MOUNT" 2> /dev/null
241        rmdir "$ISO_MOUNT"
242    fi
243    exit
244 }
245
246 function abort () {
247    echo "$COMMAND : Aborting"
248    message "Cleaning $NODE_ISO"
249    rm -f "$NODE_ISO"
250    cleanup
251 }
252
253 function main () {
254
255    trap abort int hup quit err
256    set -e
257
258    [[ -n "$DEBUG" ]] && set -x
259
260    # accept -b as -c, I am used to it now
261    while getopts "c:b:O:C:fh" opt ; do
262      case "$opt" in
263        c|b)
264 # not sure about the status of this, you are warned
265 #        echo "The custom option in $COMMAND is broken " ; exit 1
266          echo "WARNING : The custom option in $COMMAND is maybe broken "
267          echo -n "You will have been warned, proceed ? "
268          read answer
269          CUSTOM_DIR="$OPTARG" ;;
270        C)
271          cd "$OPTARG" ;;
272        O)
273          NODEOUTPUT="$OPTARG" ;;
274        f)
275          FORCE_OUTPUT=true ;;
276        h|*)
277          usage ;;
278      esac
279    done
280
281    shift $(($OPTIND-1))
282    
283    [[ -z "$@" ]] && usage
284    ISO_GENERIC="$1"; shift
285
286    if [[ -z "$@" ]] ; then
287      nodes="$DEFAULT_TARGET"
288    else
289      nodes="$@"
290    fi
291
292    ### getting image type
293    iso="${ISO_GENERIC%.iso}"
294    usb="${ISO_GENERIC%.usb}"
295    
296    if [ -d "$ISO_GENERIC" ] ; then
297      IMAGE_TYPE=dir
298    elif [ "$ISO_GENERIC" != "$iso" ] ; then
299      IMAGE_TYPE=iso
300    elif [ "$ISO_GENERIC" != "$usb" ] ; then
301      IMAGE_TYPE=usb
302    else
303      echo "Could not figure type of $ISO_GENERIC -- exiting"
304      exit 1
305    fi
306
307 #  perform that later (lazily)
308 #  so that (1st) node-dep checking are done before we bother to unpack
309 #   startup
310
311    for NODE_CONFIG in $nodes ; do
312
313      if [ "$NODE_CONFIG" = "$DEFAULT_TARGET" ] ; then
314        NODE_DEP=""
315        # default node without customization does not make sense
316        if [ -z "$CUSTOM_DIR" ] ; then
317          message "creating a non-custom node-indep. image refused\n(Would have no effect)"
318          continue
319        else
320          NODENAME="$DEFAULT_TARGET"
321          NODEOUTPUT=$(basename "$CUSTOM_DIR")
322        fi
323      else
324        NODE_DEP=true
325        NODENAME=$(host_name "$NODE_CONFIG")
326        case "$NODENAME" in
327          .*|*.)
328            message "HOST_NAME or DOMAIN_NAME not found in $NODE_CONFIG - skipped"
329            continue ;;
330        esac
331        if [ -z "$NODEOUTPUT" ] ; then
332            if [ -z "$CUSTOM_DIR" ] ; then
333               NODEOUTPUT="$NODENAME"
334            else
335               NODEOUTPUT="${NODENAME}"-$(basename "$CUSTOM_DIR")
336            fi
337        fi
338      fi
339
340      message "$COMMAND : dealing with node $NODENAME"
341
342      if [[ "$IMAGE_TYPE" = "iso" ||  "$IMAGE_TYPE" = "dir" ]] ; then
343        NODE_ISO="$NODEOUTPUT.iso"
344        NODE_LOG="$NODEOUTPUT-iso.log"
345      else
346        NODE_ISO="$NODEOUTPUT.usb"
347        NODE_LOG="$NODEOUTPUT-usb.log"
348      fi
349
350      ### checking
351      if [ -e  "$NODE_ISO" ] ; then
352        if [ -n "$FORCE_OUTPUT" ] ; then
353          message "$NODE_ISO exists, will overwrite (-f)"
354          rm "$NODE_ISO"
355        else
356          message "$NODE_ISO exists, please remove first - skipped" ; continue
357        fi
358      fi
359      if [ -n "$NODE_DEP" -a ! -f "$NODE_CONFIG" ] ; then
360        message "Could not find node-specifig config - skipped" ; continue
361      fi
362      
363      startup
364
365      if [ -n "$NODE_DEP" ] ; then
366        verbose "Pushing node config into overlay image"
367        mkdir -p "$OVERLAY_ROOT"/"$PLNODE_PATH"
368        cp "$NODE_CONFIG" "$OVERLAY_ROOT"/"$PLNODE_PATH"/"$PLNODE"
369      else
370        verbose "Cleaning node config for node-indep. image"
371        rm -f "$OVERLAY_ROOT"/"$PLNODE_PATH"/"$PLNODE"
372      fi
373
374      echo "$COMMAND : Creating overlay image for $NODENAME"
375      (cd "$OVERLAY_ROOT" ; find . | cpio $CPIO_OARGS) | gzip -9 > "$ISO_ROOT"/overlay.img
376
377      if [[ "$IMAGE_TYPE" = "iso"  || "$IMAGE_TYPE" = "dir" ]] ; then
378        ### ISO
379        message "Refreshing isolinux.cfg"
380        # Calculate ramdisk size (total uncompressed size of both archives)
381        
382        ##########
383        # N.B. Thierry Parmentelat - 2006-06-28
384        # the order in which these images need to be mentioned here for
385        # isolinux involved some - not so educated - guesses
386        # as per syslinux source code in syslinux/runkernel.inc, the
387        # config file is parsed left to right, and indeed it's in that
388        # order that the files are loaded right off the CD
389        # This does not tell however, in case a given file is present in
390        # two different images - and that's the very purpose here - which
391        # one will take precedence over the other
392        # I came up with this order on a trial-and-error basis, I would
393        # have preferred to find it described somewhere
394        # Might be worth checking with other versions of syslinux in case
395        # the custom files would turn out to not be taken into account
396        ##########
397
398        if [ -n "$CUSTOM_DIR" ] ; then
399          images="bootcd.img custom.img overlay.img"
400        else
401          images="bootcd.img overlay.img"
402        fi
403        
404        ramdisk_size=$(cd "$ISO_ROOT" ; gzip -l $images | tail -1 | awk '{ print $2; }') # bytes
405        # keep safe, provision for cpio's block size
406        ramdisk_size=$(($ramdisk_size / 1024 + 1)) # kilobytes
407        
408        initrd_images=$(echo "$images" | sed -e 's/ /,/g')
409        # Write isolinux configuration
410        cat > "$ISO_ROOT"/isolinux.cfg <<EOF
411 DEFAULT kernel
412 APPEND ramdisk_size=$ramdisk_size initrd=$initrd_images root=/dev/ram0 rw
413 DISPLAY pl_version
414 PROMPT 0
415 TIMEOUT 40
416 EOF
417
418        message-n "Writing custom image, log on $NODE_LOG .. "
419        mkisofs -o "$NODE_ISO" -R -allow-leading-dots -J -r -b isolinux.bin \
420        -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table \
421        "$ISO_ROOT" > "$NODE_LOG" 2>&1
422        message-done
423
424      else
425        ### USB
426        umount "$NODE_ISO"
427      fi
428      node_cleanup
429      
430      message "Image for $NODENAME in $NODE_ISO"
431    done
432
433    cleanup
434
435 }
436
437 ####################
438 main "$@"