#!/bin/bash
PGM=`basename $0`
RSYNC_OPTIONS="--force -rltWDEgopt"
# List of extra dirs to create under /mnt.
OPTIONAL_MNT_DIRS="clone mnt sda sdb rpi0 rpi1"
# Where to mount the disk filesystems to be rsynced.
CLONE=/mnt/clone
CLONE_LOG=/var/log/$PGM.log
HOSTNAME=`hostname`
SRC_BOOT_PARTITION_TYPE=`parted /dev/mmcblk0 -ms p | grep "^1" | cut -f 5 -d:`
SRC_ROOT_PARTITION_TYPE=`parted /dev/mmcblk0 -ms p | grep "^2" | cut -f 5 -d:`
if [ `id -u` != 0 ]
then
echo -e "$PGM muss als root.\n ausgeführt werden"
exit 1
fi
usage()
{
echo ""
echo "Anwendung: $PGM sdN {-f|--force-initialize} {-v|--verbose}"
echo " Beispiel: $PGM sda"
echo " -v - listet alle Dateien, die kopiert werden"
echo " -f - erzwingt Initialisieren der Zielpartitionen"
echo ""
echo " Klont (rsync) ein laufendes Raspberry Pi Dateisystem zu einer Ziel-SD-Card 'sdN',"
echo " die in einen Pi-USB-Port eingesteckt ist (über einen USB-Card-Reader)."
echo " $PGM kann das laufende System zu einer neuen SD-Card clonen oder ein inkrementelles"
echo " rsync auf eine existierende Raspberry Pi Backup-SD-Card durchführen."
echo ""
echo " Wenn die Ziel-SD-Card eine existierende $SRC_BOOT_PARTITION_TYPE Partition 1 und eine"
echo " $SRC_ROOT_PARTITION_TYPE Partition 2 hat, nimmt $PGM (sofern -f nicht gesetzt ist)"
echo " an, dass die SD-Card ein existierendes Backup mit Partitionen in den korrekten"
echo " Größen ist und für einen Raspberry Pi angelegt wurde. Es müssen nur die Partitionen"
echo " gemountet werden und ein rsync zum laufenden System durchgeführt werden."
echo ""
echo " Wenn die Partitionen nicht gefunden werden (oder -f gesetzt ist), fragt $PGM,"
echo " ob es OK ist, die Ziel-Partitionen auf der SD-Card zu initialisieren."
echo " Das wird erledigt durch ein partielles 'dd' vom laufenden Boot-Laufwerk"
echo " /dev/mmcblk0 zur Ziel-SD-Card /dev/sdN, gefolgt von einem fdisk rezise und"
echo " mkfs.ext4 der /dev/sdN Partition 2."
echo " Das erzeugt eine komplette $SRC_BOOT_PARTITION_TYPE Partition 1 mit allen Boot-Dateien"
echo " und eine leere Partition 2 rootfs in der richtigen Größe."
echo " Die SD-Card-Partitionen werden anschließend gemountet und ein rsync zum laufenden"
echo " System durchgeführt."
echo ""
echo " Die Ziel-Partitionen auf der SD-Card werden gemountet auf $CLONE."
echo " In die Datei $CLONE_LOG wird ein Protokoll geschrieben."
echo " Achte darauf, dass keine anderen Programme mit Disk-Schreibzugriffen laufen"
echo " während $PGM aktiv ist."
echo ""
exit 0
}
VERBOSE=off
while [ "$1" ]
do
case "$1" in
-v|--verbose)
VERBOSE=on
RSYNC_OPTIONS=${RSYNC_OPTIONS}v
;;
-f|--force-initialize)
FORCE_INITIALIZE=true
;;
*)
if [ "$DST_DISK" != "" ]
then
echo "Falsche Argumente!"
usage
fi
DST_DISK=$1
;;
esac
shift
done
if [ "$DST_DISK" = "" ]
then
usage
exit 0
fi
if ! cat /proc/partitions | grep -q $DST_DISK
then
echo "Ziellaufwerk '$DST_DISK' existiert nicht."
echo "Stecke die Ziel-SD-Card in einen USB-Port."
echo "Wenn sie nicht als '$DST_DISK' angezeigt wird, mache ein"
echo -e "'cat /proc/partitions' um zu sehen, wo sie sein könnte.\n"
exit 0
fi
TEST_MOUNTED=`fgrep " $CLONE " /etc/mtab | cut -f 1 -d ' ' `
if [ "$TEST_MOUNTED" != "" ]
then
echo "Dieses Script benutzt $CLONE für gemountete Dateisysteme, aber"
echo "$CLONE ist bereits mit $TEST_MOUNTED gemountet."
echo -e "sudo umount $CLONE vor Ausführung dieses Scripts.\n"
exit 0
fi
if [ ! -d $CLONE ]
then
MNT_MOUNT=`fgrep " /mnt " /etc/mtab | cut -f 1 -d ' ' `
if [ "$MNT_MOUNT" = "" ]
then
mkdir $CLONE
else
echo "$MNT_MOUNT ist aktuell gemountet auf /mnt."
echo -e "sudo umount /mnt vor Ausführung von $PGM.\n"
exit 0
fi
fi
# Borrowed from do_expand_rootfs in raspi-config
expand_rootfs()
{
# Get the starting offset of the root partition
PART_START=$(parted /dev/mmcblk0 -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 ===========
#
DST_ROOT_PARTITION=/dev/${DST_DISK}2
DST_BOOT_PARTITION=/dev/${DST_DISK}1
# Check that none of the destination partitions are busy (mounted), if so => umount.
#
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 "Mindestens eine Zielpartition wird benutzt (ist gemountet)."
echo " Mountstatus:"
echo " $DST_ROOT_PARTITION: $DST_ROOT_CURMOUNT"
echo " $DST_BOOT_PARTITION: $DST_BOOT_CURMOUNT"
echo "umount der Zielpartition(en)..."
if [ "$DST_ROOT_CURMOUNT" != "" ]
then
echo "'sudo umount $DST_ROOT_PARTITION' wird ausgeführt..."
sudo umount $DST_ROOT_PARTITION
fi
if [ "$DST_BOOT_CURMOUNT" != "" ]
then
echo "'sudo umount $DST_BOOT_PARTITION' wird ausgeführt..."
sudo umount $DST_BOOT_PARTITION
fi
echo "==============================="
# echo -e "Abbruch!\n"
# exit 0
fi
# 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:`
if [ "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" ] || \
[ "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" ] || \
[ "$FORCE_INITIALIZE" = "true" ]
then
echo ""
if [ "$FORCE_INITIALIZE" = "true" ]
then
echo "*** Erzwinge Initialisierung der Zielpartition '$DST_DISK' ***"
fi
echo "Die existierenden Partitionen auf der Zieldisk '$DST_DISK' sind:"
# fdisk -l /dev/$DST_DISK | grep $DST_DISK
parted /dev/$DST_DISK unit MB p \
| sed "/^Model/d ; /^Sector/d"
if [ "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" ]
then
echo -e " ... Kann kein Ziel-Boot-Dateisystem finden vom Typ: $SRC_BOOT_PARTITION_TYPE\n"
fi
if [ "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" ]
then
echo -e " ... Kann kein Ziel-Root-Dateisystem finden vom Typ: $SRC_ROOT_PARTITION_TYPE\n"
fi
echo "Dieses Script kann die Ziel-Disk initialisieren mit einer Partitionsstrukur, die"
echo "vom aktuall gebooteten Dateisystem kopiert wird, und dann Partition 2 (das Root-"
echo "Dateisystem) anpassen, um den freien Platz auf der SD-Card nutzen zu können."
echo -n "Soll das Ziel /dev/$DST_DISK initialisiert werden? (ja/nein): "
read resp
if [ "$resp" = "j" ] || [ "$resp" = "ja" ]
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/mmcblk0 -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 "Bilde die Partitionsstruktur, kopiere $DD_COUNT Megabytes..."
dd if=/dev/mmcblk0 of=/dev/$DST_DISK bs=1M count=$DD_COUNT
# 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 "Dimensioniere Partition 2 (Root-Partition), um den gesamten Speicherplatz"
echo "der SD-Card nutzen zu können..."
expand_rootfs
mkfs.ext4 $DST_ROOT_PARTITION > /dev/null
echo ""
echo "/dev/$DST_DISK ist initialisiert und dimensioniert. Die Partitionen sind:"
# 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/mmcblk0p2`
echo ""
echo "Das existierende Label des gebooteten Laufwerks /dev/mmcblk0p2 rootfs ist: $SRC_ROOT_VOL_NAME"
echo -n "Du kannst nun ein Label für das Ziellaufwerk rootfs $DST_ROOT_PARTITION eingeben: "
read resp
if [ "$resp" != "" ]
then
e2label $DST_ROOT_PARTITION $resp
fi
else
echo -e "Abbruch!\n"
exit 0
fi
fi
# =========== Setup Summary ===========
#
DST_ROOT_VOL_NAME=`e2label $DST_ROOT_PARTITION`
if [ "$DST_ROOT_VOL_NAME" = "" ]
then
DST_ROOT_VOL_NAME="no label"
fi
echo ""
echo "Klone Ziellaufwerk : $DST_DISK"
echo "Klone Ziel rootfs : $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on ${CLONE}"
echo "Klone Ziel bootfs : $DST_BOOT_PARTITION on ${CLONE}/boot"
echo "Verbose Modus : $VERBOSE"
echo "==============================="
# If this is an SD card initialization, can watch progress of the clone
# in another terminal with: watch df -h
#
echo -n "Letzter Check: Ist es OK, wenn mit dem Klonen fortgefahren wird (ja/nein)?: "
read resp
if [ "$resp" != "j" ] && [ "$resp" != "ja" ]
then
echo -n "Willst du das Disk-Klonen wirklich abbrechen (ja/nein)?: "
read resp
if [ "$resp" != "n" ] && [ "$resp" != "nein" ]
then
echo "==============================="
echo -e "Abbruch des Disk-Klonens.\n"
exit 0
fi
fi
#
# =========== End of Setup ===========
# Mount destination filesystems.
echo "=> Mounten von $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) auf $CLONE"
if ! mount $DST_ROOT_PARTITION $CLONE
then
echo -e "Mountfehler von $DST_ROOT_PARTITION, Abbruch!\n"
exit 0
fi
if [ ! -d $CLONE/boot ]
then
mkdir $CLONE/boot
fi
echo "=> Mounten von $DST_BOOT_PARTITION auf $CLONE/boot"
if ! mount $DST_BOOT_PARTITION $CLONE/boot
then
umount $CLONE
echo -e "Mountfehler von $DST_BOOT_PARTITION, Abbruch!\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
echo "Starten von rsync des Dateisystems mit $DST_DISK"
echo -n "(Dies kann einige Zeit dauern, bitte Geduld)... "
rsync $RSYNC_OPTIONS --delete \
--exclude '.gvfs' \
--exclude '/dev' \
--exclude '/media' \
--exclude '/mnt' \
--exclude '/proc' \
--exclude '/run' \
--exclude '/sys' \
--exclude '/tmp' \
--exclude 'lost\+found' \
// \
$CLONE
# Fixup some stuff
#
for i in dev media mnt proc run sys
do
if [ ! -d $CLONE/$i ]
then
mkdir $CLONE/$i
fi
done
if [ ! -d $CLONE/tmp ]
then
mkdir $CLONE/tmp
chmod a+w $CLONE/tmp
fi
# Some extra optional dirs I create under /mnt
for i in $OPTIONAL_MNT_DIRS
do
if [ ! -d $CLONE/mnt/$i ]
then
mkdir $CLONE/mnt/$i
fi
done
rm -f $CLONE/etc/udev/rules.d/70-persistent-net.rules
DATE=`date '+%F %H:%M'`
echo "$DATE $HOSTNAME $PGM : klone auf $DST_DISK ($DST_ROOT_VOL_NAME)" \
>> $CLONE_LOG
echo "$DATE $HOSTNAME $PGM : klone auf $DST_DISK ($DST_ROOT_VOL_NAME)" \
>> $CLONE/$CLONE_LOG
STOP_TIME=`date '+%H:%M:%S'`
echo ""
echo "*** Fertig mit klonen auf /dev/$DST_DISK ***"
echo " Gestartet: $START_TIME Beendet: $STOP_TIME"
echo ""
echo "==============================="
echo ""
echo "Wenn du noch etwas an dem Ergebnis kontrollieren oder ändern willst"
echo "(z. B. $CLONE/etc/hostname, $CLONE/etc/network/interfaces etc.,"
echo "weil die geklonte SD-Card für einen anderen Pi ist), starte jetzt"
echo "eine andere Terminalsitzung und mache die Änderungen in dieser."
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 $CLONE/etc/hostname, $CLONE/etc/network/interfaces, etc
# if I'm cloning into a card to be installed on another Pi.
#
echo -n "Ansonsten drücke Enter zum Dismounten der /dev/$DST_DISK Partitionen..."
read resp
echo "$CLONE/boot dismounten"
umount $CLONE/boot
echo "$CLONE dismounten"
umount $CLONE
echo "==============================="
exit 0