supports partial bootcd in bootcd-custom, merges with CD if found
[bootcd.git] / cdcustom.sh
1 #!/bin/bash
2
3 # purpose : create a node-specific CD ISO 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 image and nothing else
11 # (*) or you want to generate several iso 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 # given a (generic, node-independant) CD ISO image, and a (set of)
16 # node-specific config file(s), this command creates a new almost
17 # identical ISO image with the node config file embedded as
18 # /usr/boot/plnode.txt in the overlay.img image
19 # the output iso images are named after the nodes, and stored in .
20
21 ######## Logic
22 # here is how we do this
23 # for efficiency, we do only once:
24 #   (*) mount the generic iso
25 #   (*) copy it into a temp dir
26 #   (*) unzip/unarchive overlay image into another temp dir
27 # then for each node, we
28 #   (*) insert plnode.txt at the right place
29 #   (*) rewrap a gzipped/cpio overlay.img, that we push onto the
30 #       copied iso tree
31 #   (*) rewrap this into an iso image
32 # and cleanup/umount everything 
33
34 ######## Customizing the BootCD
35 # In addition we check (once) for
36 #  (*) a file called 'bootcd.img' in the current dir
37 #  (*) a directory named 'bootcd/' in the current dir
38 # if any of those is present, we use this - presumably custom - stuff to
39 # replace original bootcd.img from the CD
40 # more precisely:
41 #  (*) if the .img is present, it is taken as-is,
42 #  (*) if not but bootcd/ is present, bootcd.img is refreshed and used
43 #  (*) if not but bootcd-custom is present, its content is merged into
44 #      the bootcd structure as extracted from the generic CD,
45 #      then cpio/compressed.
46 # All this is done only once at startup because it typically
47 #  takes 40s to recompress bootcd.img
48
49 ######## Implementation note
50 # in a former release it was possible to perform faster by
51 # loopback-mounting the generic iso image
52 # Unfortunately mkisofs cannot graft a file that already exists on the
53 # original tree (so overlay.img cannot be overridden)
54 # to make things worse we cannot loopback-mount the cpio-gzipped
55 # overlay image either, so all this stuff is way more complicated
56 # than it used to be.
57 # It's still pretty fast, unless recompressing a bootcd.img is required
58
59 set -e 
60 COMMAND=$(basename $0 .sh)
61
62 function usage () {
63
64    echo "Usage: $0 generic-iso node-config [node-configs]"
65    echo "Creates a node-specific ISO image"
66    echo "with the node-specific config file embedded as /boot/plnode.txt"
67    exit 1
68 }
69
70 ### read config file in a subshell and echoes host_name
71 function host_name () {
72   export CONFIG=$1; shift
73   ( . "$CONFIG" ; echo $HOST_NAME )
74 }
75
76 ### Globals
77 OVERLAY_IMAGE=overlay.img
78 PLNODE_PATH=/usr/boot
79 PLNODE=plnode.txt
80 # use local bootcd/ or bootcd.img if existing
81 BOOTCD_IMAGE=bootcd.img
82 BOOTCD_ROOT=bootcd
83 BOOTCD_CUSTOM=bootcd-custom
84 ## arg-provided generic iso
85 ISO_GENERIC=
86 # node-dep conf file
87 NODE_CONFIG=
88 # resulting iso image and log
89 NODE_ISO=
90 NODE_LOG=
91 ## mount points and temps
92 ISO_MOUNT=/tmp/$COMMAND-$$-mount
93 ISO_ROOT=/tmp/$COMMAND-$$-iso
94 OVERLAY_ROOT=/tmp/$COMMAND-$$-overlay
95 # node-dep cpio/gzip image
96 NODE_OVERLAY=
97
98 CPIO_OARGS="-oc --quiet"
99 CPIO_IARGS="-id --quiet"
100 CPIO_PARGS="-pdu --quiet"
101
102 # export VERBOSE=true for enabling this
103 function verbose () {
104    if [ -n "$VERBOSE" ] ; then
105      echo "$@"
106    fi
107  }
108
109 function message () { echo "$COMMAND : $@" ; }
110 function message-n () { echo -n "$COMMAND : $@" ; }
111 function message-done () { echo Done ; }
112 function error () { echo "$COMMAND : ERROR $@ - exiting" ; exit 1 ;}
113
114 # lazy startup
115 STARTED_UP=
116 function startup () {
117
118    [[ -n "$DEBUG" ]] && set -x
119
120    # lazy : run only once
121    [[ -n "$STARTED_UP" ]] && return
122    message "lazy start up"
123
124    ### checking
125    [ ! -f "$ISO_GENERIC" ] && error "Could not find template ISO image"
126    [ -d "$ISO_MOUNT" ] && error "$ISO_MOUNT already exists" 
127    [ -d "$ISO_ROOT" ] && error "$ISO_ROOT already exists" 
128    [ -d "$OVERLAY_ROOT" ] && error "$OVERLAY_ROOT already exists"
129
130    verbose "Creating temp dirs"
131    mkdir -p $ISO_MOUNT $ISO_ROOT $OVERLAY_ROOT
132    verbose "Mounting generic ISO $ISO_GENERIC under $ISO_MOUNT"
133    mount -o ro,loop $ISO_GENERIC $ISO_MOUNT
134
135    ### DONT!! use tar for duplication
136    message "Duplicating ISO image in $ISO_ROOT"
137    (cd $ISO_MOUNT ; find . | cpio $CPIO_PARGS  $ISO_ROOT )
138
139    prepare_bootcd
140
141    message "Extracting generic overlay image in $OVERLAY_ROOT"
142    gzip -d -c "$ISO_ROOT/$OVERLAY_IMAGE" | ( cd "$OVERLAY_ROOT" ; cpio $CPIO_IARGS )
143
144    STARTED_UP=true
145
146 }   
147
148 ### handle custom bootcd
149 # see above for the logic in there
150 function prepare_bootcd () {
151
152    custom_bootcd=
153
154    if [ -f "$BOOTCD_IMAGE" ] ; then
155      custom_bootcd=true
156      message "Using local $BOOTCD_IMAGE as-is"
157    elif [ -d "$BOOTCD_ROOT" ] ; then
158      custom_bootcd=true
159      message-n "Using local $BOOTCD_ROOT, compressing .. "
160      (cd $BOOTCD_ROOT ; find . | cpio $CPIO_OARGS) | gzip -9 > $BOOTCD_IMAGE
161      message-done
162    elif [ -d "$BOOTCD_CUSTOM" ] ; then
163      custom_bootcd=true
164      message "Found custom partial bootcd $BOOTCD_CUSTOM"
165      message-n "Extracting standard $BOOTCD_ROOT locally .. "
166      mkdir $BOOTCD_ROOT
167      gzip -d -c $ISO_ROOT/$BOOTCD_IMAGE | (cd $BOOTCD_ROOT ; cpio $CPIO_IARGS )
168      message-done
169      message-n "Pushing contents of $BOOTCD_CUSTOM/etc into $BOOTCD_ROOT .. "
170      (cd $BOOTCD_CUSTOM ; tar cf - ./etc ) | ( cd $BOOTCD_ROOT ; tar xf -)
171      message-done
172      message-n "Compressing custom $BOOTCD_ROOT .. "
173      (cd $BOOTCD_ROOT ; find . | cpio $CPIO_OARGS) | gzip -9 > $BOOTCD_IMAGE
174      message-done
175    fi
176
177    if [ -n "$custom_bootcd" ] ; then
178      message "WARNING : You are using a custom bootcd"
179      message-n "Pushing custom $BOOTCD_IMAGE onto $ISO_ROOT.. "
180      cp $BOOTCD_IMAGE $ISO_ROOT/$BOOTCD_IMAGE
181      message-done
182    fi
183      
184 }
185
186 function node_cleanup () {
187    verbose "Cleaning node-dependent cpio image"
188    rm -rf "$NODE_OVERLAY"
189   
190 }
191
192 function cleanup () {
193
194    echo "$COMMAND : cleaning up"
195    [[ -n "$DEBUG" ]] && set -x
196
197    verbose "Cleaning overlay image"
198    rm -rf "$OVERLAY_ROOT"
199    verbose "Cleaning ISO image"
200    rm -rf "$ISO_ROOT"
201    verbose "Cleaning node-dep overlay image"
202    rm -f "$NODE_OVERLAY"
203    verbose "Unmounting $ISO_MOUNT"
204    umount "$ISO_MOUNT" 2> /dev/null
205    rmdir "$ISO_MOUNT"
206    exit
207 }
208
209 function abort () {
210    echo "$COMMAND : Aborting"
211    message "Cleaning $NODE_ISO"
212    rm -f "$NODE_ISO"
213    cleanup
214 }
215
216 function main () {
217
218    trap abort int hup quit err
219    set -e
220
221    [[ -n "$DEBUG" ]] && set -x
222
223    [[ -z "$@" ]] && usage
224    ISO_GENERIC=$1; shift
225
226    [[ -z "$@" ]] && usage
227
228 #  perform that later (lazily)
229 #  so that (1st) node-dep checking are done before we bother to unpack
230 #   startup
231
232    for NODE_CONFIG in "$@" ; do
233
234      NODENAME=$(host_name $NODE_CONFIG)
235      if [ -z "$NODENAME" ] ; then
236        message "HOST_NAME not found in $NODE_CONFIG - skipped"
237        continue
238      fi
239    
240      message "$COMMAND : dealing with node $NODENAME"
241
242      NODE_ISO="$NODENAME.iso"
243      NODE_LOG="$NODENAME.log"
244      NODE_OVERLAY="$NODENAME.img"
245
246      ### checking
247      if [ -e  "$NODE_ISO" ] ; then
248        message "$NODE_ISO exists, please remove first - skipped" ; continue
249      fi
250      if [ ! -f "$NODE_CONFIG" ] ; then
251        message "Could not find node-specifig config - skipped" ; continue
252      fi
253      
254      startup
255
256      verbose "Pushing node config into overlay image"
257      mkdir -p $OVERLAY_ROOT/$PLNODE_PATH
258      cp "$NODE_CONFIG" $OVERLAY_ROOT/$PLNODE_PATH/$PLNODE
259
260      echo "$COMMAND : Creating overlay image for $NODENAME"
261      (cd "$OVERLAY_ROOT" ; find . | cpio $CPIO_OARGS) | gzip -9 > $NODE_OVERLAY
262
263      message-n "Pushing custom overlay image "
264      cp "$NODE_OVERLAY" "$ISO_ROOT/$OVERLAY_IMAGE"
265      message-done
266
267      message "Refreshing isolinux.cfg"
268      # Calculate ramdisk size (total uncompressed size of both archives)
269      ramdisk_size=$(gzip -l $ISO_ROOT/bootcd.img $ISO_ROOT/overlay.img | tail -1 | awk '{ print $2; }') # bytes
270      # keep safe, provision for cpio's block size
271      ramdisk_size=$(($ramdisk_size / 1024 + 1)) # kilobytes
272
273      # Write isolinux configuration
274      cat > $ISO_ROOT/isolinux.cfg <<EOF
275 DEFAULT kernel
276 APPEND ramdisk_size=$ramdisk_size initrd=bootcd.img,overlay.img root=/dev/ram0 rw
277 DISPLAY pl_version
278 PROMPT 0
279 TIMEOUT 40
280 EOF
281
282      message-n "Writing custom image, log on $NODE_LOG .. "
283      mkisofs -o "$NODE_ISO" -R -allow-leading-dots -J -r -b isolinux.bin \
284      -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table \
285      "$ISO_ROOT" > "$NODE_LOG" 2>&1
286      message-done
287    
288      node_cleanup
289      
290      message "CD ISO image for $NODENAME in $NODE_ISO"
291    done
292
293    cleanup
294
295 }
296
297 ####################
298 main "$@"