Non-root users cannot chown.
[bootcd.git] / build.sh
1 #!/bin/bash
2 #
3 # Builds custom BootCD ISO and USB images in the current
4 # directory. For backward compatibility, if an old-style static
5 # configuration is specified, that configuration file will be parsed
6 # instead of the current PLC configuration in
7 # /etc/planetlab/plc_config.
8 #
9 # Aaron Klingaman <alk@absarokasoft.com>
10 # Mark Huang <mlhuang@cs.princeton.edu>
11 # Copyright (C) 2004-2007 The Trustees of Princeton University
12 #
13 # $Id$
14 #
15
16 PATH=/sbin:/bin:/usr/sbin:/usr/bin
17
18 CONFIGURATION=default
19 NODE_CONFIGURATION_FILE=
20 TYPES="usb iso usb_serial iso_serial"
21 # Leave 4 MB of free space
22 FREE_SPACE=4096
23 CUSTOM_DIR=
24 OUTPUT_BASE=
25 MKISOFS_OPTS="-R -J -r -f -b isolinux.bin -c boot.cat -no-emul-boot -boot-load-size 4 -boot-info-table"
26
27 usage()
28 {
29     echo "Usage: build.sh [OPTION]..."
30     echo "    -c name          (Deprecated) Static configuration to use (default: $CONFIGURATION)"
31     echo "    -f planet.cnf    Node to customize CD for (default: none)"
32     echo "    -t 'types'       Build the specified images (default: $TYPES)"
33     echo "    -a               Build all supported images"
34     echo "    -C custom-dir    Custom directory"
35     echo "    -O output-base   The basename of the generated files (default: PLC_NAME-BootCD-VERSION)"
36     echo "    -h               This message"
37     exit 1
38 }
39
40 # Get options
41 while getopts "O:c:f:t:C:ah" opt ; do
42     case $opt in
43     c)
44         CONFIGURATION=$OPTARG
45         ;;
46     f)
47         NODE_CONFIGURATION_FILE=$OPTARG
48         ;;
49     t)
50         TYPES="$OPTARG"
51         ;;
52     C)
53         CUSTOM_DIR="$OPTARG"
54         ;;
55     O)
56         OUTPUT_BASE="$OPTARG"
57         ;;
58     a)
59         TYPES="usb iso usb_serial iso_serial usb_cramfs iso_cramfs usb_cramfs_serial iso_cramfs_serial"
60         ;;
61     h|*)
62         usage
63         ;;
64     esac
65 done
66
67 # Do not tolerate errors
68 set -e
69
70 # Change to our source directory
71 srcdir=$(cd $(dirname $0) && pwd -P)
72 pushd $srcdir
73
74 # Root of the isofs
75 isofs=$PWD/build/isofs
76
77 # The reference image is expected to have been built by prep.sh (see .spec)
78 # we disable the initial logic that called prep.sh if that was not the case
79 # this is because prep.sh needs to know pldistro 
80 if [ ! -f $isofs/bootcd.img -o ! -f build/version.txt ] ; then
81     echo "You have to run prep.sh prior to calling $0 - exiting"
82     exit 1
83 fi
84
85 # build/version.txt written by prep.sh
86 BOOTCD_VERSION=$(cat build/version.txt)
87
88 if [ -f /etc/planetlab/plc_config ] ; then
89     # Source PLC configuration
90     . /etc/planetlab/plc_config
91 fi
92
93 ### This support for backwards compatibility can be taken out in the
94 ### future. RC1 based MyPLCs set $PLC_BOOT_SSL_CRT in the plc_config
95 ### file, but >=RC2 based bootcd assumes that $PLC_BOOT_CA_SSL_CRT is
96 ### set.
97 if [ -z "$PLC_BOOT_CA_SSL_CRT" -a ! -z "$PLC_BOOT_SSL_CRT" ] ; then
98     PLC_BOOT_CA_SSL_CRT=$PLC_BOOT_SSL_CRT
99 fi
100
101 # If PLC configuration is not valid, try a static configuration
102 if [ -z "$PLC_BOOT_CA_SSL_CRT" -a -d configurations/$CONFIGURATION ] ; then
103     # (Deprecated) Source static configuration
104     . configurations/$CONFIGURATION/configuration
105     PLC_NAME="PlanetLab"
106     PLC_MAIL_SUPPORT_ADDRESS="support@planet-lab.org"
107     PLC_WWW_HOST="www.planet-lab.org"
108     PLC_WWW_PORT=80
109     if [ -n "$EXTRA_VERSION" ] ; then
110     BOOTCD_VERSION="$BOOTCD_VERSION $EXTRA_VERSION"
111     fi
112     PLC_BOOT_HOST=$PRIMARY_SERVER
113     PLC_BOOT_SSL_PORT=$PRIMARY_SERVER_PORT
114     PLC_BOOT_CA_SSL_CRT=configurations/$CONFIGURATION/$PRIMARY_SERVER_CERT
115     PLC_ROOT_GPG_KEY_PUB=configurations/$CONFIGURATION/$PRIMARY_SERVER_GPG
116 fi
117
118 FULL_VERSION_STRING="$PLC_NAME BootCD $BOOTCD_VERSION"
119
120 echo "* Building images for $FULL_VERSION_STRING"
121
122 # From within a myplc chroot /usr/tmp is too small 
123 # to build all possible images, whereas /data is part of the host
124 # filesystem and usually has sufficient space.  What we
125 # should do is check whether the expected amount of space
126 # is available.
127 BUILDTMP=/usr/tmp
128 if [ -d /data/tmp ] ; then
129     isreadonly=$(mktemp /data/tmp/isreadonly.XXXXXX || /bin/true)
130     if [ -n "$isreadonly" ] ; then
131         rm -f "$isreadonly"
132         BUILDTMP=/data/tmp
133     fi
134 fi
135
136 declare -a _CLEANUPS=()
137 function do_cleanup()
138 {
139     cd /
140     for i in "${_CLEANUPS[@]}"; do
141         $i
142     done
143 }
144 function push_cleanup()
145 {
146     _CLEANUPS=( "${_CLEANUPS[@]}" "$*" )
147 }
148 function pop_cleanup()
149 {
150     unset _CLEANUPS[$((${#_CLEANUPS[@]} - 1))]
151 }
152
153 trap "do_cleanup" ERR INT EXIT
154
155 BUILDTMP=$(mktemp -d ${BUILDTMP}/bootcd.XXXXXX)
156 push_cleanup rm -fr "${BUILDTMP}"
157 mkdir "${BUILDTMP}/isofs"
158 for i in "$isofs"/{bootcd.img,kernel,isolinux.bin}; do
159     ln -s "$i" "${BUILDTMP}/isofs"
160 done
161 isofs="${BUILDTMP}/isofs"
162
163 # Root of the ISO and USB images
164 echo "* Populating root filesystem..."
165 overlay="${BUILDTMP}/overlay"
166 install -d -m 755 $overlay
167 push_cleanup rm -fr $overlay
168
169 # Create version files
170 echo "* Creating version files"
171
172 # Boot Manager compares pl_version in both places to make sure that
173 # the right CD is mounted. We used to boot from an initrd and mount
174 # the CD on /usr. Now we just run everything out of the initrd.
175 for file in $overlay/pl_version $overlay/usr/isolinux/pl_version ; do
176     mkdir -p $(dirname $file)
177     echo "$FULL_VERSION_STRING" >$file
178 done
179
180 # Install boot server configuration files
181 echo "* Installing boot server configuration files"
182
183 # We always intended to bring up and support backup boot servers,
184 # but never got around to it. Just install the same parameters for
185 # both for now.
186 for dir in $overlay/usr/boot $overlay/usr/boot/backup ; do
187     install -D -m 644 $PLC_BOOT_CA_SSL_CRT $dir/cacert.pem
188     install -D -m 644 $PLC_ROOT_GPG_KEY_PUB $dir/pubring.gpg
189     echo "$PLC_BOOT_HOST" >$dir/boot_server
190     echo "$PLC_BOOT_SSL_PORT" >$dir/boot_server_port
191     echo "/boot/" >$dir/boot_server_path
192 done
193
194 # (Deprecated) Install old-style boot server configuration files
195 install -D -m 644 $PLC_BOOT_CA_SSL_CRT $overlay/usr/bootme/cacert/$PLC_BOOT_HOST/cacert.pem
196 echo "$FULL_VERSION_STRING" >$overlay/usr/bootme/ID
197 echo "$PLC_BOOT_HOST" >$overlay/usr/bootme/BOOTSERVER
198 echo "$PLC_BOOT_HOST" >$overlay/usr/bootme/BOOTSERVER_IP
199 echo "$PLC_BOOT_SSL_PORT" >$overlay/usr/bootme/BOOTPORT
200
201 # Generate /etc/issue
202 echo "* Generating /etc/issue"
203
204 if [ "$PLC_WWW_PORT" = "443" ] ; then
205     PLC_WWW_URL="https://$PLC_WWW_HOST/"
206 elif [ "$PLC_WWW_PORT" != "80" ] ; then
207     PLC_WWW_URL="http://$PLC_WWW_HOST:$PLC_WWW_PORT/"
208 else
209     PLC_WWW_URL="http://$PLC_WWW_HOST/"
210 fi
211
212 mkdir -p $overlay/etc
213 cat >$overlay/etc/issue <<EOF
214 $FULL_VERSION_STRING
215 $PLC_NAME Node: \n
216 Kernel \r on an \m
217 $PLC_WWW_URL
218
219 This machine is a node in the $PLC_NAME distributed network.  It has
220 not fully booted yet. If you have cancelled the boot process at the
221 request of $PLC_NAME Support, please follow the instructions provided
222 to you. Otherwise, please contact $PLC_MAIL_SUPPORT_ADDRESS.
223
224 Console login at this point is restricted to root. Provide the root
225 password of the default $PLC_NAME Central administrator account at the
226 time that this CD was created.
227
228 EOF
229
230 # Set root password
231 echo "* Setting root password"
232
233 if [ -z "$ROOT_PASSWORD" ] ; then
234     # Generate an encrypted password with crypt() if not defined
235     # in a static configuration.
236     ROOT_PASSWORD=$(python <<EOF
237 import crypt, random, string
238 salt = [random.choice(string.letters + string.digits + "./") for i in range(0,8)]
239 print crypt.crypt('$PLC_ROOT_PASSWORD', '\$1\$' + "".join(salt) + '\$')
240 EOF
241 )
242 fi
243
244 # build/passwd copied out by prep.sh
245 sed -e "s@^root:[^:]*:\(.*\)@root:$ROOT_PASSWORD:\1@" build/passwd \
246     >$overlay/etc/passwd
247
248 # Install node configuration file (e.g., if node has no floppy disk or USB slot)
249 if [ -f "$NODE_CONFIGURATION_FILE" ] ; then
250     echo "* Installing node configuration file"
251     install -D -m 644 $NODE_CONFIGURATION_FILE $overlay/usr/boot/plnode.txt
252 fi
253
254 # Pack overlay files into a compressed archive
255 echo "* Compressing overlay image"
256 (cd $overlay && find . | cpio --quiet -c -o) | gzip -9 >$isofs/overlay.img
257
258 rm -rf $overlay
259 pop_cleanup
260
261 if [ -n "$CUSTOM_DIR" ]; then
262     echo "* Compressing custom image"
263     (cd "$CUSTOM_DIR" && find . | cpio --quiet -c -o) | gzip -9 >$isofs/custom.img
264 fi
265
266 # Calculate ramdisk size (total uncompressed size of both archives)
267 ramdisk_size=$(gzip -l $isofs/bootcd.img $isofs/overlay.img ${CUSTOM_DIR:+$isofs/custom.img} | tail -1 | awk '{ print $2; }') # bytes
268 ramdisk_size=$((($ramdisk_size + 1023) / 1024)) # kilobytes
269
270 echo "$FULL_VERSION_STRING" >$isofs/pl_version
271
272 popd
273
274 function build_iso()
275 {
276     local iso="$1"
277     local serial=$2
278     local custom=$3
279
280     # Write isolinux configuration
281     cat >$isofs/isolinux.cfg <<EOF
282 ${serial:+SERIAL 0 115200}
283 DEFAULT kernel
284 APPEND ramdisk_size=$ramdisk_size initrd=bootcd.img,overlay.img${custom:+,custom.img} root=/dev/ram0 rw ${serial:+console=ttyS0,115200n8}
285 DISPLAY pl_version
286 PROMPT 0
287 TIMEOUT 40
288 EOF
289
290     # Create ISO image
291     echo "* Creating ISO image"
292     mkisofs -o "$iso" \
293         $MKISOFS_OPTS \
294         $isofs
295 }
296
297 # Create USB image
298 function build_usb()
299 {
300     echo -n "* Creating USB image... "
301     local usb="$1"
302     local serial=$2
303     local custom=$3
304
305     mkfs.vfat -C "$usb" $(($(du -Lsk $isofs | awk '{ print $1; }') + $FREE_SPACE))
306
307     # Populate it
308     echo -n " populating USB image... "
309     mcopy -bsQ -i "$usb" "$isofs"/* ::/
310
311     # Use syslinux instead of isolinux to make the image bootable
312     tmp="${BUILDTMP}/syslinux.cfg"
313     cat >$tmp <<EOF
314 ${serial:+SERIAL 0 115200}
315 DEFAULT kernel
316 APPEND ramdisk_size=$ramdisk_size initrd=bootcd.img,overlay.img${custom:+,custom.img} root=/dev/ram0 rw ${serial:+console=ttyS0,115200n8}
317 DISPLAY pl_version
318 PROMPT 0
319 TIMEOUT 40
320 EOF
321     mdel -i "$usb" ::/isolinux.cfg 2>/dev/null || :
322     mcopy -i "$usb" "$tmp" ::/syslinux.cfg
323     rm -f "$tmp"
324
325     echo "making USB image bootable."
326     $srcdir/syslinux/unix/syslinux "$usb"
327 }
328
329
330 # Setup CRAMFS related support
331 function prepare_cramfs()
332 {
333     [ -n "$CRAMFS_PREPARED" ] && return 0
334     local custom=$1
335
336     echo "* Setting up CRAMFS-based images"
337     local tmp="${BUILDTMP}/cramfs-tree"
338     mkdir -p "$tmp"
339     push_cleanup rm -rf $tmp
340     pushd $tmp
341     gzip -d -c $isofs/bootcd.img     | cpio -diu
342     gzip -d -c $isofs/overlay.img    | cpio -diu
343     [ -n "$custom" ] && \
344         gzip -d -c $isofs/custom.img | cpio -diu
345
346     # clean out unnecessary rpm lib
347     echo "* clearing var/lib/rpm/*"
348     rm -f var/lib/rpm/*
349
350     # bootcd requires this directory
351     mkdir -p mnt/confdevice
352
353     # relocate various directory to /tmp
354     rm -rf root
355     ln -fs /tmp/root root
356     ln -fs /sbin/init linuxrc 
357     ln -fs /tmp/resolv.conf etc/resolv.conf
358     ln -fs /tmp/etc/mtab etc/mtab
359
360     # have pl_rsysinit copy over appropriate etc & var directories into /tmp/etc/
361     # make /tmp/etc
362     echo "* renaming dirs in ./etc"
363     pushd etc
364     for dir in `find * -type d -prune | grep -v rc.d`; do
365         mv ${dir} ${dir}_o
366         ln -fs /tmp/etc/${dir} ${dir}
367     done
368     popd
369
370     echo "* renaming dirs in ./var"
371     # rename all top-level directories and put in a symlink to /tmp/var
372     pushd var
373     for dir in `find * -type d -prune`; do
374         mv ${dir} ${dir}_o
375         ln -fs /tmp/var/${dir} ${dir}
376     done
377     popd
378
379     # overwrite fstab to mount / as cramfs and /tmp as tmpfs
380     echo "* Overwriting etc/fstab to use cramfs and tmpfs"
381     rm -f ./etc/fstab
382     cat >./etc/fstab <<EOF
383 /dev/ram0     /              cramfs     ro              0 0
384 none          /dev/pts       devpts     gid=5,mode=620  0 0
385 none          /proc          proc       defaults        0 0
386 none          /sys           sysfs      defaults        0 0
387 EOF
388
389     pushd dev
390     rm -f console
391     mknod console c 5 1
392     #for i in 0 1 2 3 4 5 6 7 8; do rm -f ram${i} ; done
393     #for i in 0 1 2 3 4 5 6 7 8; do mknod ram${i} b 1 ${i} ; done
394     #ln -fs ram1 ram
395     #ln -fs ram0 ramdisk
396     popd
397
398     # update etc/inittab to start with pl_rsysinit
399     sed -i 's,pl_sysinit,pl_rsysinit,' etc/inittab
400
401     # modify inittab to have a serial console
402     echo "T0:23:respawn:/sbin/agetty -L ttyS0 9600 vt100" >> etc/inittab
403     # and let root log in
404     echo "ttyS0" >> etc/securetty
405
406     # calculate the size of /tmp based on the size of /etc & /var + 8MB slack
407     etcsize=$(du -s ./etc | awk '{ print $1 }')
408     varsize=$(du -s ./var | awk '{ print $1 }')
409     let msize=($varsize+$etcsize+8192)/1024
410
411
412     # generate pl_rsysinit
413     cat > etc/rc.d/init.d/pl_rsysinit <<EOF
414 #!/bin/sh
415 # generated by build.sh
416 echo -n "pl_rsysinit: preparing /etc and /var for pl_sysinit..."
417 mount -t tmpfs -orw,size=${msize}M,mode=1777 tmpfs /tmp
418 mkdir -p /tmp/root
419 mkdir -p /tmp/etc
420 touch /tmp/etc/resolv.conf
421 touch /tmp/etc/mtab
422 mkdir -p /tmp/var
423
424 # make mtab happy
425 echo "tmpfs /tmp tmpfs rw,size=${msize}M,mode=1777 1 1" > /tmp/etc/mtab
426
427 # copy over directory contents of all _o directories from /etc and /var
428 # /tmp/etc and /tmp/var
429 pushd /etc
430 for odir in \$(cd /etc && ls -d *_o); do dir=\$(echo \$odir | sed 's,\_o$,,'); (mkdir -p /tmp/etc/\$dir && cd \$odir && find . | cpio -p -d -u /tmp/etc/\$dir); done
431 popd
432 pushd /var
433 for odir in \$(cd /var && ls -d *_o); do dir=\$(echo \$odir | sed 's,\_o$,,'); (mkdir -p /tmp/var/\$dir && cd \$odir && find . | cpio -p -d -u /tmp/var/\$dir); done
434 popd
435
436 echo "done"
437 # hand over to pl_sysinit
438 echo "pl_rsysinit: handing over to pl_sysinit"
439 /etc/init.d/pl_sysinit
440 EOF
441     chmod +x etc/rc.d/init.d/pl_rsysinit
442
443     popd
444
445     # create the cramfs image
446     echo "* Creating cramfs image"
447     mkfs.cramfs $tmp/ ${BUILDTMP}/cramfs.img
448     cramfs_size=$(($(du -sk ${BUILDTMP}/cramfs.img | awk '{ print $1; }') + 1))
449     rm -rf $tmp
450     pop_cleanup
451 }
452
453 # Create ISO CRAMFS image
454 function build_iso_cramfs()
455 {
456     local iso="$1"
457     local serial=$2
458     prepare_cramfs $3
459     echo "* Creating ISO CRAMFS-based image"
460
461     local tmp="${BUILDTMP}/cramfs-iso"
462     mkdir -p "$tmp"
463     push_cleanup rm -rf $tmp
464     (cd $isofs && find . | grep -v "\.img$" | cpio -p -d -u $tmp/)
465     cat >$tmp/isolinux.cfg <<EOF
466 ${serial:+SERIAL 0 9600}
467 DEFAULT kernel
468 APPEND ramdisk_size=$cramfs_size initrd=cramfs.img root=/dev/ram0 ro ${serial:+console=ttyS0,9600n8}
469 DISPLAY pl_version
470 PROMPT 0
471 TIMEOUT 40
472 EOF
473
474     cp ${BUILDTMP}/cramfs.img $tmp
475     mkisofs -o "$iso" \
476         "$MKISOFS_OPTS" \
477         $tmp
478
479     rm -fr "$tmp"
480     pop_cleanup
481 }
482
483 # Create USB CRAMFS based image
484 function build_usb_cramfs()
485 {
486     local usb="$1"
487     local serial=$2
488     prepare_cramfs $3
489     echo "* Creating USB CRAMFS based image"
490
491     let vfat_size=${cramfs_size}+$FREE_SPACE
492
493     # Make VFAT filesystem for USB
494     mkfs.vfat -C "$usb" $vfat_size
495
496     # Populate it
497     echo "* Populating USB with overlay images and cramfs"
498     mcopy -bsQ -i "$usb" $isofs/kernel $isofs/pl_version ::/
499     mcopy -bsQ -i "$usb" ${BUILDTMP}/cramfs.img ::/
500
501     # Use syslinux instead of isolinux to make the image bootable
502     tmp="${BUILDTMP}/syslinux.cfg"
503     cat >$tmp <<EOF
504 ${serial:+SERIAL 0 9600}
505 DEFAULT kernel
506 APPEND ramdisk_size=$cramfs_size initrd=cramfs.img root=/dev/ram0 ro ${serial:+console=ttyS0,9600n8}
507 DISPLAY pl_version
508 PROMPT 0
509 TIMEOUT 40
510 EOF
511   mcopy -bsQ -i "$usb" "$tmp" ::/syslinux.cfg
512   rm -f "$tmp"
513
514   echo "* Making USB CRAMFS based image bootable"
515   $srcdir/syslinux/unix/syslinux "$usb"
516 }
517
518 function type_to_name()
519 {
520     echo $1 | sed '
521         s/usb$/.usb/;
522         s/usb_serial$/-serial.usb/;
523         s/iso$/.iso/;
524         s/iso_serial$/-serial.iso/;
525         s/usb_cramfs$/-cramfs.usb/;
526         s/usb_cramfs_serial$/-cramfs-serial.usb/;
527         s/iso_cramfs$/-cramfs.iso/;
528         s/iso_cramfs_serial$/-cramfs-serial.iso/;
529         '
530 }
531
532 [ -z "$OUTPUT_BASE" ] && OUTPUT_BASE="$PLC_NAME-BootCD-$BOOTCD_VERSION"
533
534 for t in $TYPES; do
535     serial=
536     tname=`type_to_name $t`
537     if [[ "$t" == *_serial ]]; then
538         serial=1
539         t=`echo $t | sed 's/_serial$//'`
540     fi
541     build_$t "${OUTPUT_BASE}${tname}" $serial $CUSTOM_DIR
542 done
543
544 exit 0