Clone a sdcard with Raspberry

Add a USB based card reader, insert the SD card you want to work on:

Use the script:

https://github.com/KoljaWindeler/pi-clone

Afterwards you can access the cloned SD card to make more alterations.

#!/bin/bash
 
PGM=`basename $0`
RSYNC_OPTIONS="--force -rltWDEgopt"
#-r recursive
#-l copy symlinks as symlinks
#-t preserve modification time
#-W copy whole files
#-D
#-E preserve file executability
#-g preserve group
#-o preserve owner
#-p preerve permission
#-t preserver times
 
 
# List of extra dirs to create under /mnt.
OPTIONAL_MNT_DIRS=""
 
# Where to mount the disk filesystems to be rsynced.
DEST_ROOT_PATH=/mnt/clone
SRC_ROOT_PATH=/mnt/source
 
CLONE_LOG=/var/log/$PGM.log
 
HOSTNAME=`hostname`
 
# e.g. mmcblk0->p<-1, we dont need that in sdb-><-1 ...
PARTITION_SPACER="p"
 
# check if we are root
if &#91; `id -u` != 0 &#93;
then
    echo -e "$PGM needs to be run as root.\n"
    exit 1
fi
 
# check if we have rsync installed
if ! rsync --version > /dev/null
then
        echo -e "\nOoops! $PGM needs the rsync program but cannot run it."
        echo "Make sure rsync is installed:"
        echo "    $ sudo apt-get update"
        echo -e "    $ sudo apt-get install rsync\n"
        exit 0
fi
 
if test -e /sbin/fsck.vfat
then
        HAVE_FSCK_VFAT="yes"
else
 
        echo "[Note] fsck.vfat was not found."
        echo "It is recommended to install dosfstools:"
        echo "    $ sudo apt-get update"
        echo "    $ sudo apt-get install dosfstools"
fi
 
usage()
        {
        echo ""
        echo "usage: $PGM sdN sdM {-f|--force-initialize} {-v|--verbose} {-y|--yes-to-all}"
        echo "    Example:  $PGM sda"
	echo "              to clone the current running system from /dev/mmcblk0"
        echo "    Example:  $PGM sda sdb "
	echo "              to restore the system from /dev/sda to /dev/sdb"
        echo "    -v - list all files as they are copied."
        echo "    -f - force initialize the destination partitions"
        echo "    -y - skip all questions"
        echo ""
        echo "    $PGM can run in two ways"
        echo "    1) $PGM sdN (without sdM) will cloning your current running system"
	echo "       to a SD card installed on sdN"
        echo "    2) $PGM sdN sdM will clone the system on disk sdN to the disk sdM"
        echo ""
        echo "    $PGM can clone the system to a new SD card or can"
        echo "    incrementally rsync to existing backup Raspberry/Banana Pi SD cards."
        echo ""
        echo "    If the destination SD card has an existing $SRC_BOOT_PARTITION_TYPE partition 1 and a"
        echo "    $SRC_ROOT_PARTITION_TYPE partition 2, $PGM assumes (unless using the -f option)"
        echo "    that the SD card is an existing backup with the partitions"
        echo "    properly sized and set up for a Raspberry/Banana Pi.  All that is needed"
        echo "    is to mount the partitions and rsync them to the source system."
        echo ""
        echo "    If these partitions are not found (or -f), then $PGM will ask"
        echo "    if it is OK to initialize the destination SD card partitions."
        echo "    This is done by a partial 'dd' from the source device /dev/mmcblk0"
        echo "    or /dev/sdN to the destination SD card followed by a"
        echo "    fdisk resize and mkfs.ext4 of partition 2."
        echo "    This creates a completed $SRC_BOOT_PARTITION_TYPE partition 1 containing all boot"
        echo "    files and an empty but properly sized partition 2 rootfs."
        echo "    The SD card  partitions are then mounted and rsynced to the"
        echo "    running system."
        echo ""
        echo "    The SD card destination partitions will be mounted on $DEST_ROOT_PATH."
        echo "    A log will be written to $CLONE_LOG."
        echo "    Avoid running other disk writing programs when running $PGM."
        echo ""
        exit 0
        }
 
VERBOSE=off
RESTORE_MODE=0
NO_REQUEST=false
 
while [ "$1" ]
do
        case "$1" in
                -v|--verbose)
                        VERBOSE=on
                        RSYNC_OPTIONS=${RSYNC_OPTIONS}v
                        ;;
                -f|--force-initialize)
                        FORCE_INITIALIZE=true
                        ;;
                -h|--help)
                        usage
                        ;;
                -y|--yes-to-all)
                        NO_REQUEST=true
                        ;;
                *)
                        if [ "$DST_DISK" != "" ]
                        then
                                if [ "$SRC_DISK" != "" ]
                                then
                                        echo "Bad args"
                                        usage
                                fi
                                SRC_DISK=$DST_DISK
                                RESTORE_MODE=1
                                PARTITION_SPACER=""
                        fi
                        DST_DISK=$1
                        ;;
        esac
        shift
done
 
if [ "$SRC_DISK" = "" ]
then
        SRC_DISK=mmcblk0
fi
 
SRC_BOOT_PARTITION_TYPE=`parted /dev/$SRC_DISK -ms p | grep "^1" | cut -f 5 -d:`
SRC_ROOT_PARTITION_TYPE=`parted /dev/$SRC_DISK -ms p | grep "^2" | cut -f 5 -d:`
 
if [ "$DST_DISK" = "" ]
then
        usage
        exit 0
fi
 
 
if ! cat /proc/partitions | grep -q $DST_DISK
then
        echo "Destination disk '$DST_DISK' does not exist."
        echo "Plug the destination SD card into a USB port."
        echo "If it does not show up  as '$DST_DISK', then do a"
        echo -e "'cat /proc/partitions' to see where it might be.\n"
        exit 0
fi
 
if [ "$RESTORE_MODE" = "1" ]
then
        if ! cat /proc/partitions | grep -q $SRC_DISK
        then
                echo "Source disk '$SRC_DISK' does not exist."
                echo "Plug the destination SD card into a USB port."
                echo "If it does not show up  as '$SRC_DISK', then do a"
                echo -e "'cat /proc/partitions' to see where it might be.\n"
                exit 0
        fi
fi
 
unmount_or_abort()
        {
        echo -n "Do you want to unmount $1? (yes/no): "
        if [ "$NO_REQUEST" = "true" ]
        then
         resp="y"
         echo "yes"
        else
         read resp
        fi
        if [ "$resp" = "y" ] || [ "$resp" = "yes" ]
        then
                if ! umount $1
                then
                        echo "Sorry, $PGM could not unmount $1."
                        echo -e "Aborting!\n"
                        exit 0
                fi
        else
                echo -e "Aborting!\n"
                exit 0
        fi
        }
 
DST_ROOT_PARTITION=/dev/${DST_DISK}2
DST_BOOT_PARTITION=/dev/${DST_DISK}1
 
SRC_ROOT_PARTITION=/dev/${SRC_DISK}${PARTITION_SPACER}2
SRC_BOOT_PARTITION=/dev/${SRC_DISK}${PARTITION_SPACER}1
 
# Check that none of the destination partitions are busy (mounted).
# src partitions can be mounted as we are just reading from them
#
DST_ROOT_CURMOUNT=`fgrep "$DST_ROOT_PARTITION " /etc/mtab | cut -f 2 -d ' ' `
DST_BOOT_CURMOUNT=`fgrep "$DST_BOOT_PARTITION " /etc/mtab | cut -f 2 -d ' ' `
 
if [ "$DST_ROOT_CURMOUNT" != "" ] || [ "$DST_BOOT_CURMOUNT" != "" ]
then
        echo "A destination partition is busy (mounted).  Mount status:"
        echo "    $DST_ROOT_PARTITION:  $DST_ROOT_CURMOUNT"
        echo "    $DST_BOOT_PARTITION:  $DST_BOOT_CURMOUNT"
        if [ "$DST_BOOT_CURMOUNT" != "" ]
        then
                unmount_or_abort $DST_BOOT_CURMOUNT
        fi
        if [ "$DST_ROOT_CURMOUNT" != "" ]
        then
                unmount_or_abort $DST_ROOT_CURMOUNT
        fi
fi
 
# check that no other drive is mounted on our mountpoint for destination
TEST_MOUNTED=`fgrep " $DEST_ROOT_PATH " /etc/mtab | cut -f 1 -d ' ' `
if [ "$TEST_MOUNTED" != "" ]
then
        echo "This script uses $DEST_ROOT_PATH for mounting filesystems, but"
        echo "$DEST_ROOT_PATH is already mounted with $TEST_MOUNTED."
        unmount_or_abort $DEST_ROOT_PATH
fi
 
# check that no other drive is mounted on our mountpoint for source
if [ "$RESTORE_MODE" = "1" ]
then
        TEST_MOUNTED=`fgrep " $SRC_ROOT_PATH/boot " /etc/mtab | cut -f 1 -d ' ' `
        if [ "$TEST_MOUNTED" != "" ]
        then
                echo "This script uses $SRC_ROOT_PATH/boot for mounting filesystems, but"
                echo "$SRC_ROOT_PATH/boot is already mounted with $TEST_MOUNTED."
                unmount_or_abort $SRC_ROOT_PATH"/boot"
        fi
 
        TEST_MOUNTED=`fgrep " $SRC_ROOT_PATH " /etc/mtab | cut -f 1 -d ' ' `
        if [ "$TEST_MOUNTED" != "" ]
        then
                echo "This script uses $SRC_ROOT_PATH for mounting filesystems, but"
                echo "$SRC_ROOT_PATH is already mounted with $TEST_MOUNTED."
                unmount_or_abort $SRC_ROOT_PATH
        fi
fi
 
# check that /mnt is not mounted on another drive
if [ ! -d $DEST_ROOT_PATH ]
then
        MNT_MOUNT=`fgrep " /mnt " /etc/mtab | cut -f 1 -d ' ' `
        if [ "$MNT_MOUNT" != "" ]
        then
                echo "$MNT_MOUNT is currently mounted on /mnt."
                unmount_or_abort /mnt
        fi
 
        mkdir $DEST_ROOT_PATH
fi
 
# ensure source mount dir is available
if [ "$RESTORE_MODE" = "1" ]
then
        if [ ! -d $SRC_ROOT_PATH ]
        then
                mkdir $SRC_ROOT_PATH
        fi
fi
 
 
 
 
# Borrowed from do_expand_rootfs in raspi-config
expand_rootfs()
        {
        # Get the starting offset of the root partition
        PART_START=$(parted /dev/$SRC_DISK -ms unit s p | grep "^2" | cut -f 2 -d:)
        [ "$PART_START" ] || return 1
        # Return value will likely be error for fdisk as it fails to reload the
        # partition table because the root fs is mounted
        fdisk /dev/$DST_DISK > /dev/null <<EOF
p
d
2
n
p
2
$PART_START
 
p
w
q
EOF
        }
 
 
# =========== Disk Setup and Checks ===========
#
# Check that destination partitions are the right type.
#
DST_BOOT_PARTITION_TYPE=`parted /dev/$DST_DISK -ms p \
                | grep "^1" | cut -f 5 -d:`
DST_ROOT_PARTITION_TYPE=`parted /dev/$DST_DISK -ms p \
                | grep "^2" | cut -f 5 -d:`
SRC_BOOT_PARTITION_SIZE=`parted /dev/$SRC_DISK -ms p \
                | grep "^1" | cut -f 3 -d:`
DST_BOOT_PARTITION_SIZE=`parted /dev/$DST_DISK -ms p \
                | grep "^1" | cut -f 3 -d:`

 
if &#91; "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" &#93; || \
   &#91; "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" &#93; || \
   &#91; "$SRC_BOOT_PARTITION_SIZE" != "$DST_BOOT_PARTITION_SIZE" &#93; || \
   &#91; "$FORCE_INITIALIZE" = "true" &#93;
then
        echo ""
        if &#91; "$FORCE_INITIALIZE" = "true" &#93;
        then
                echo "*** Forcing a partition initialization of destination '$DST_DISK' ***"
        fi
 
        echo "The existing partitions on destination disk '$DST_DISK' are:"
#       fdisk -l /dev/$DST_DISK | grep $DST_DISK
        parted /dev/$DST_DISK unit MB p \
                | sed "/^Model/d ; /^Sector/d"
        if &#91; "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" &#93;
        then
                echo "  ... Cannot find a destination boot file system of type: $SRC_BOOT_PARTITION_TYPE"
                echo ""
        fi
        if &#91; "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" &#93;
        then
                echo "  ... Cannot find a destination root file system of type: $SRC_ROOT_PARTITION_TYPE"
                echo ""
        fi
        if &#91; "$SRC_BOOT_PARTITION_SIZE" != "$DST_BOOT_PARTITION_SIZE" &#93;
        then
                echo " ... The boot partitions have different sizes." 
                echo "     Required $SRC_BOOT_PARTITION_SIZE vs $DST_BOOT_PARTITION_SIZE available on $DST_DISK"
                echo ""
        fi
        echo "This script can initialize the destination disk with a partition"
        echo "structure copied from the source filesytem and then resize"
        echo "partition 2 (the root filesystem) to use all space on the SD card."
        echo -n "Do you want to initialize the destination /dev/$DST_DISK? (yes/no): "
        if &#91; "$NO_REQUEST" = "true" &#93;
        then
         resp="y"
         echo "yes"
        else
         read resp
        fi
        if &#91; "$resp" = "y" &#93; || &#91; "$resp" = "yes" &#93;
        then
                # Image onto the destination disk a beginning fragment of the
                # running SD card file structure that spans at least more than
                # the start of partition 2.
                #
                # Calculate the start of partition 2 in MB for the dd.
                PART2_START=$(parted /dev/$SRC_DISK -ms unit MB p | grep "^2" \
                                | cut -f 2 -d: | sed s/MB// | tr "," "." | cut -f 1 -d.)
                # and add some slop
                DD_COUNT=`expr $PART2_START + 8`
 
                echo ""
                echo "Imaging the partition structure, copying $DD_COUNT megabytes..."
                sync
                dd if=/dev/$SRC_DISK of=/dev/$DST_DISK bs=1M count=$DD_COUNT
 
                # Partition was copied live so fsck to clean up for possible future
                # "Volume was not properly unmounted" warnings.
                if &#91; "$HAVE_FSCK_VFAT" = "yes" &#93;
                then
                        echo "Running fsck on $DST_BOOT_PARTITION..."
                        fsck -p $DST_BOOT_PARTITION &> /dev/null
                fi
 
                # But, though Partion 1 is now imaged, partition 2 is incomplete and
                # maybe the wrong size for the destination SD card.  So fdisk it to
                # make it fill the rest of the disk and mkfs it to clean it out.
                #
                echo "Sizing partition 2 (root partition) to use all SD card space..."
                expand_rootfs
                mkfs.ext4 $DST_ROOT_PARTITION > /dev/null
 
                echo ""
                echo "/dev/$DST_DISK is initialized and resized.  Its partitions are:"
#               fdisk -l /dev/$DST_DISK | grep $DST_DISK
                parted /dev/$DST_DISK unit MB p \
                        | sed "/^Model/d ; /^Sector/d"
 
                SRC_ROOT_VOL_NAME=`e2label /dev/${SRC_DISK}${PARTITION_SPACER}2`
 
                echo ""
                echo "Your booted /dev/${SRC_DISK}${PARTITION_SPACER}2 rootfs existing label: $SRC_ROOT_VOL_NAME"
                echo -n "You may enter a label for the destination rootfs $DST_ROOT_PARTITION: "
                if [ "$NO_REQUEST" = "true" ]
                then
                 resp=""
                 echo "skipped"
                else
                 read resp
                fi
                if [ "$resp" != "" ]
                then
                        e2label $DST_ROOT_PARTITION $resp
                fi
        else
                echo -e "Aborting\n"
                exit 0
        fi
fi
 
 
# =========== Setup Summary ===========
#
DST_ROOT_VOL_NAME=`e2label $DST_ROOT_PARTITION`
SRC_ROOT_VOL_NAME=`e2label $SRC_ROOT_PARTITION`
 
 
if [ "$DST_ROOT_VOL_NAME" = "" ]
then
        DST_ROOT_VOL_NAME="no label"
fi
 
if [ "$SRC_ROOT_VOL_NAME" = "" ]
then
        SRC_ROOT_VOL_NAME="no label"
fi
 
 
echo ""
if [ "$RESTORE_MODE" = "1" ]
then
        echo "Restore mode             :  yes"
        echo "Source disk              :  $SRC_DISK"
        echo "Source bootfs            :  $SRC_BOOT_PARTITION on ${SRC_ROOT_PATH}/boot"
        echo "Source rootfs            :  $SRC_ROOT_PARTITION ($SRC_ROOT_VOL_NAME) on ${SRC_ROOT_PATH}"
else
        echo "Restore mode             :  no"
fi
echo "Clone destination disk   :  $DST_DISK"
echo "Clone destination bootfs :  $DST_BOOT_PARTITION on ${DEST_ROOT_PATH}/boot"
echo "Clone destination rootfs :  $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on ${DEST_ROOT_PATH}"
echo "Verbose mode             :  $VERBOSE"
 
echo "==============================="
 
 
# If this is an SD card initialization, can watch progress of the clone
# in another terminal with:  watch df -h
#
echo -n "Final check, is it Ok to proceed with the clone (yes/no)?: "
if [ "$NO_REQUEST" = "true" ]
then
 resp="y"
 echo "yes"
else
 read resp
fi
if [ "$resp" != "y" ] && [ "$resp" != "yes" ]
then
        echo -e "Aborting the disk clone.\n"
        exit 0
fi
 
#
# =========== End of Setup  ===========
 
# mount the root filesystem if we are using the restore mode
if [ "$RESTORE_MODE" = "1" ]
then
        echo "=> Mounting /dev/${SRC_DISK}${PARTITION_SPACER}2 ($SRC_ROOT_VOL_NAME) on ${SRC_ROOT_PATH}"
 
        if ! mount /dev/${SRC_DISK}${PARTITION_SPACER}2 ${SRC_ROOT_PATH}
        then
                echo -e "Mount failure on ${SRC_ROOT_PATH}, aborting!\n"
                exit 0
        fi
fi
 
# ensure that the boot partition is mounted before running rsync
SRC_BOOT_PATH="/boot"
if [ "$RESTORE_MODE" = "1" ]
then
        SRC_BOOT_PATH="${SRC_ROOT_PATH}/boot"
        if [ ! -d ${BOOT_PATH} ]
        then
                mkdir ${BOOT_PATH}
        fi
fi
 
BOOT_MOUNT=`fgrep " ${SRC_BOOT_PATH} " /etc/mtab | cut -f 1 -d ' ' `
if [ "$BOOT_MOUNT" = "" ]
        then
        echo "=> Mounting /dev/${SRC_DISK}${PARTITION_SPACER}1 on ${SRC_BOOT_PATH}"
        if [ ! -d ${SRC_BOOT_PATH} ]
        then
                mkdir -p ${SRC_BOOT_PATH}
        fi
 
        if ! mount /dev/${SRC_DISK}${PARTITION_SPACER}1 ${SRC_BOOT_PATH}
        then
                echo -e "Mount failure on ${SRC_BOOT_PATH}, aborting!\n"
                exit 0
        fi
fi
 
# Mount destination filesystems.
echo "=> Mounting $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on $DEST_ROOT_PATH"
if ! mount $DST_ROOT_PARTITION $DEST_ROOT_PATH
then
        echo -e "Mount failure of $DST_ROOT_PARTITION, aborting!\n"
        exit 0
fi
 
if [ ! -d $DEST_ROOT_PATH/boot ]
then
        mkdir -p $DEST_ROOT_PATH/boot
fi
 
echo "=> Mounting $DST_BOOT_PARTITION on $DEST_ROOT_PATH/boot"
if ! mount $DST_BOOT_PARTITION $DEST_ROOT_PATH/boot
then
        umount $DEST_ROOT_PATH
        echo -e "Mount failure of $DST_BOOT_PARTITION, aborting!\n"
        exit 0
fi
 
echo "==============================="
 
 
 
 
START_TIME=`date '+%H:%M:%S'`
 
# Exclude fuse mountpoint .gvfs, various other mount points, and tmpfs
# file systems from the rsync.
#
sync
 
RSYNC_SRC_PATH="/"
if [ "$RESTORE_MODE" = "1" ]
then
        RSYNC_SRC_PATH="${SRC_ROOT_PATH}/"
fi
 
echo "Starting the filesystem rsync ${RSYNC_SRC_PATH} to $DEST_ROOT_PATH"
echo -n "(This may take several minutes)..."
 
rsync $RSYNC_OPTIONS --delete \
                --exclude '.gvfs' \
                --exclude ${RSYNC_SRC_PATH}'dev' \
                --exclude ${RSYNC_SRC_PATH}'media' \
                --exclude ${RSYNC_SRC_PATH}'mnt' \
                --exclude ${RSYNC_SRC_PATH}'proc' \
                --exclude ${RSYNC_SRC_PATH}'run' \
                --exclude ${RSYNC_SRC_PATH}'sys' \
                --exclude ${RSYNC_SRC_PATH}'tmp' \
                --exclude ${RSYNC_SRC_PATH}'lost\+found' \
        $RSYNC_SRC_PATH \
        $DEST_ROOT_PATH
 
 
# Fixup some stuff
#
 
for i in dev media mnt proc run sys
do
        if [ ! -d $DEST_ROOT_PATH/$i ]
        then
                mkdir $DEST_ROOT_PATH/$i
        fi
done
 
if [ ! -d $DEST_ROOT_PATH/tmp ]
then
        mkdir $DEST_ROOT_PATH/tmp
        chmod a+w $DEST_ROOT_PATH/tmp
fi
 
# Some extra optional dirs I create under /mnt
for i in $OPTIONAL_MNT_DIRS
do
        if [ ! -d $DEST_ROOT_PATH/mnt/$i ]
        then
                mkdir $DEST_ROOT_PATH/mnt/$i
        fi
done
 
 
#rm -f $DEST_ROOT_PATH/etc/udev/rules.d/70-persistent-net.rules
 
 
DATE=`date '+%F %H:%M'`
 
echo "$DATE  $HOSTNAME $PGM : clone to $DST_DISK ($DST_ROOT_VOL_NAME)" \
                >> $CLONE_LOG
echo "$DATE  $HOSTNAME $PGM : clone to $DST_DISK ($DST_ROOT_VOL_NAME)" \
                >> $DEST_ROOT_PATH/$CLONE_LOG
 
 
STOP_TIME=`date '+%H:%M:%S'`
 
echo ""
echo "*** Done with clone to /dev/$DST_DISK ***"
echo "    Started: $START_TIME    Finished: $STOP_TIME"
echo ""
 
# Pause before unmounting in case I want to inspect the clone results
# or need to custom modify any files on the destination SD clone.
# Eg. modify $DEST_ROOT_PATH/etc/hostname, $DEST_ROOT_PATH/etc/network/interfaces, etc
# if I'm cloning into a card to be installed on another Pi.
#
echo -n "Hit Enter when ready to unmount the used partitions..."
if [ "$NO_REQUEST" = "true" ]
then
 echo "skipped"
else
 read resp
fi
 
echo "unmounting $DEST_ROOT_PATH/boot"
umount $DEST_ROOT_PATH/boot
 
echo "unmounting $DEST_ROOT_PATH"
umount $DEST_ROOT_PATH
 
if [ "$RESTORE_MODE" = "1" ]
then
        echo "unmounting $SRC_ROOT_PATH/boot"
        umount $SRC_ROOT_PATH/boot
 
        echo "unmounting $SRC_ROOT_PATH"
        umount $SRC_ROOT_PATH
fi
 
echo "==============================="
 
exit 0

Leave a Reply

Your email address will not be published. Required fields are marked *