#!/bin/bash PROGNAME=`basename $0` VERSION='$HeadURL$ $LastChangedRevision$' set -e DSTMNTPNT=/root-dest ROOTMNTPNT=/root-dest TMP_DIR=${TMP_DIR:-/var/tmp} FSTYPE=ext3 main() { # Defaults for options OPT_SIMULATE=false ROOTDEV= OPT_RECREATE=false VERBOSELEVEL=2 # Process options while [ "X$1" != X ]; do case $1 in -n|--simulate) OPT_SIMULATE=true ;; -v|--verbose) VERBOSELEVEL=3 ;; --root=*) ROOTDEV=${1#*=} ;; -h|--help) usage 0 ;; --recreate) OPT_RECREATE=true ;; -d) VERBOSELEVEL=$2; shift ;; --debug=*) VERBOSELEVEL=${1#*=} ;; --) shift; break ;; -*) usage ;; *) break ;; esac shift done # Process arguments [ "X$2" != X -a "X$3" = X ] || usage SRCDEV=$1 DSTDEV=$2 # Sanity checks sanity_checks # Clone clone # Rewrite /etc/fstab (only if the root device was provided) [ "X$ROOTDEV" = X ] || rewrite_fstab_on_clone # Rewrite /boot/grub/menu.lst (only if the root device was provided and it is also what we cloned) [ "X$ROOTDEV" = X ] || [ $ROOTDEV != $DSTDEV ] || rewrite_menulst # fsck that stuff that needs fscking [ "X$TOFSCK" = X ] || { TOFSCK="$(echo $TOFSCK | xargs -n 1 echo | sort -u | paste -d' ' -s)" for FS in $TOFSCK; do ade_std_shell "fsck $FS" done } } sanity_checks() { local DEV MNTPNT # Check not same [ "X$DSTDEV" != "X$SRCDEV" ] || error "source and destination partition appear to be the same!" # Devices that should be mounted for DEV in $SRVDEV; do [ "X$(mount | awk '{ print $1 }' | grep "^$DEV\$")" != X ] || error "$DEV: appears not to be mounted" done # Mountpoints that should be mounted for MNTPNT in ; do [ "X$(mount | awk '{ print $3 }' | grep "^$MNTPNT\$")" != X ] || error "$MNTPNT: appears not to be mounted" done # Devices that should not be mounted for DEV in $DSTDEV $ROOTDEV; do [ "X$(mount | awk '{ print $1 }' | grep "^$DEV\$")" = X ] || error "$DEV: appears to be mounted already" done # Mountpoints that should not be mounted for MNTPNT in $DSTMNTPNT; do [ "X$(mount | awk '{ print $3 }' | grep "^$MNTPNT\$")" = X ] || error "$MNTPNT: appears to be mounted already" done # Check we're root. [ -w / ] || error "it seems you are not root" ## Ask the user. #echo -n "Are you really sure you want to do this? [n]: " #read RESPONSE #[ "X$RESPONSE" = Xy ] || error "aborted" } clone() { if $OPT_RECREATE; then # Prepare destination partition info "formatting $DSTDEV ..." sleep 2 ade_std_shell "mkfs -t $FSTYPE -q $DSTDEV" fi info "$SRCDEV: cloning ..." # All functions need this to do what they have to do ade_std_shell "mkdir -p $DSTMNTPNT" ade_std_shell "mount $DSTDEV $DSTMNTPNT" # Copy source system over to destination system info "cloning ..." ade_std_shell "rsync -xa --delete $(dev2mntpnt $SRCDEV)/ $DSTMNTPNT/" # destination system doesn't need a temporary mount point for the [ ! -d $DSTMNTPNT$DSTMNTPNT ] || ade_std_shell "rmdir $DSTMNTPNT$DSTMNTPNT" # All functions should leave the clone unmounted. ade_std_shell "sync" ade_std_shell "umount $DSTMNTPNT" ade_std_shell "rmdir $DSTMNTPNT" tofsck $DSTDEV info "$SRCDEV: finished cloning" } rewrite_fstab_on_clone() { ade_std_shell "mkdir -p $ROOTMNTPNT" ade_std_shell "mount $ROOTDEV $ROOTMNTPNT" if $OPT_SIMULATE || [ -f $ROOTMNTPNT/etc/fstab ]; then # write an fstab onto the destination system. info "rewriting fstab on clone ..." ade_std_shell "perl -pi -e \"s@$SRCDEV@$DSTDEV@\" $ROOTMNTPNT/etc/fstab" sync tofsck $ROOTDEV fi ade_std_shell "umount $ROOTMNTPNT" ade_std_shell "rmdir $ROOTMNTPNT" info "$SRCDEV: finished patching fstab" } rewrite_menulst() { ade_std_shell "mkdir $ROOTMNTPNT" ade_std_shell "mount $ROOTDEV $ROOTMNTPNT" if $OPT_SIMULATE || [ -f $ROOTMNTPNT/boot/grub/menu.lst ]; then # write an fstab onto the destination system. info "rewriting menu.lst on clone ..." ade_std_shell "perl -pi -e \"s@$SRCDEV@$DSTDEV@\" $ROOTMNTPNT/boot/grub/menu.lst" sync tofsck $ROOTDEV fi ade_std_shell "umount $ROOTMNTPNT" ade_std_shell "rmdir $ROOTMNTPNT" BOOTDEV=$(expr $ROOTDEV : '\(/dev/[sh]d[a-z]\)[1-9]$') if [ "X$BOOTDEV" != X ]; then info "installing grub on MBR ..." ade_std_shell "grub-install --recheck $BOOTDEV" fi info "$SRCDEV: finished patching menu.lst" } usage() { local RC RC=${1:-1} { echo "Usage: $PROGNAME [ ] " echo echo "Options: -v | --verbose be verbose" echo " -d | --debug= be very verbose" echo " -h | --help show this text" echo " -n | --simulate do nothing for real" echo " --root= has root filesystem" echo " --recreate erase target before copying" echo } | if [ $RC = 0 ]; then cat else cat >&2 fi exit $RC } tofsck() { TOFSCK="$TOFSCK $1"; } dev2mntpnt() { local DEV MNTPNT DEV=$1 MNTPNT=$(mount | sed -n "s@$DEV on \([^ ]*\) .*@\1@p") || return $? [ "X$MNTPNT" != X ] || error "$DEV: failed to get mountpoint" debug 10 "dev2mntpnt: we think $DEV is mounted at $MNTPNT" echo $MNTPNT } ade_std_shell() { local CMD SUBSHELL PROMPT SHELLCMD # Default values for options SUBSHELL=false PROMPT= SHELLCMD=/bin/bash # Process options while [ "X$1" != X ]; do case $1 in -c) SUBSHELL=true ;; -s) SHELLCMD="$2" shift ;; -p) PROMPT="$2" shift ;; -*) internal "ade_std_shell: bad option '$1'" ;; *) break ;; esac shift done # Read arguments CMD="$1" # handle all combinations of simulation, subshells, etc. if [ "X$CMD" = X ] && $OPT_SIMULATE; then warning "no interactive shell started, as mode is simulated" elif [ "X$CMD" = X ]; then if expr $SHELLCMD : '.*/bash$'; then PS1=$PROMPT $SHELLCMD --norc else PS1=$PROMPT $SHELLCMD fi # In the special case of the 'cd' command we run it even if in simulation elif $OPT_SIMULATE && ! $SUBSHELL && [ "X${CMD%% *}" = Xcd ]; then echo "$CMD" | sed 's/[ \t][ \t]*/ /g' [ "X$(sh -c "$CMD" 2>&1)" = X ] || warning "expect next cd to fail" eval "$CMD" elif $OPT_SIMULATE ]; then echo "$CMD" | sed 's/[ ][ ]*/ /g' elif $SUBSHELL ]; then $SHELLCMD -c "$CMD" else eval "$CMD" fi } debug() { [ $VERBOSELEVEL -lt $1 ] || echo "$PROGNAME: DEBUG[$1]: $2" >&2; } info() { [ $VERBOSELEVEL -lt 3 ] || echo "$PROGNAME: INFO: $1" >&2; } warning() { [ $VERBOSELEVEL -lt 2 ] || echo "$PROGNAME: WARNING: $1" >&2; } error() { [ $VERBOSELEVEL -lt 1 ] || echo "$PROGNAME: ERROR: $1" >&2; exit 1; } internal() { echo "$PROGNAME: INTERNAL ERROR: $1" >&2; exit 2; } main "$@"