Files
OpenDRS/RPiSetup/bullseye/usr/local/bin/rpi-clone

1643 lines
37 KiB
Bash

#!/bin/bash
version=2.0.20
# auto run grub-install if grub detected
grub_auto=1
PGM=`basename $0`
setup_command="$PGM-setup"
rsync_options="--force -rltWDEHXAgoptx"
if [ `id -u` != 0 ]
then
echo -e "$PGM needs to be run as root.\n"
exit 1
fi
confirm()
{
if ((unattended || (initialize && Unattended) ))
then
return 0
fi
printf "\n%s (yes/no): " "$1"
read resp
if [ "$resp" = "y" ] || [ "$resp" = "yes" ]
then
return 0
fi
if [ "$2" == "abort" ]
then
echo -e "Aborting!\n"
exit 0
fi
return 1
}
commands="rsync parted fdisk findmnt column fsck.vfat"
packages="rsync parted util-linux mount bsdmainutils dosfstools"
need_packages=""
idx=1
for cmd in $commands
do
if ! command -v $cmd > /dev/null
then
pkg=$(echo "$packages" | cut -d " " -f $idx)
printf "%-30s %s\n" "Command not found: $cmd" "Package required: $pkg"
need_packages="$need_packages $pkg"
fi
((++idx))
done
if [ "$need_packages" != "" ]
then
confirm "Do you want to apt-get install the packages?" "abort"
apt-get install -y --no-install-recommends $need_packages
fi
clone=/mnt/clone
clone_src=/mnt/clone-src
clone_log=/var/log/$PGM.log
HOSTNAME=`hostname`
usage()
{
echo $"
usage: $PGM sdN {-v|--verbose} {-f|--force-initialize} {-f2}
{-u|--unattended} {-U|--Unattended} {-q|--quiet}
{-s|--setup host} {-e|--edit-fstab sdX } {-m|--mountdir dir }
{-L|--label-partitions label} {-l|--leave-sd-usb-boot}
{-a|--all-sync} {-F|--Force-sync} {-x} {-V|--version}
{--convert-fstab-to-partuuid}
-v - verbose rsync, list all files as they are copied.
-f - force initialize the destination disk by imaging the booted disk
partition structure. File systems are then synced or imaged.
-f2 - force initialize only the first 2 partitions to the destination.
So a multi partition USB boot can initialize clone back to
a 2 partition SD card.
-u - unattended clone if not initializing. No confirmations asked,
but abort if disk needs initializing or on error.
-U - unattended even if initializing. No confirmations asked,
but abort only on errors.
-q - quiet mode, no output unless errors or initializing. Implies -u.
-s host - add 'host' to args passed to script rpi-clone-setup and run it
after cloning but before unmounting partitions. For setting
clone disk hostname, but args can be what the script expects.
You can give multiple "-s arg" options.
-e sdX - edit destination fstab to change booted device names to new
device 'sdX'. This is Only for fstabs that use device names.
Used for setting up a USB bootable disk.
-m dir - Add dir to a custom list of mounted directories to sync. Then
the custom list will be synced instead of the default of all
mounted directories. The root directory is always synced.
Not for when initializing.
-L lbl - label for ext type partitions. If ends with '#', replace with
partition number and label all ext partitions. Otherwise,
apply label to root partition only.
-l - leave SD card to USB boot alone when cloning to SD card mmcblk0
from a USB boot. This preserves a SD card to USB boot setup
by leaving the SD card cmdline.txt using the USB root. When
cloning to USB from SD card this option sets up the SD card
cmdline.txt to boot to the USB disk.
-a - Sync all partitions if types compatible, not just mounted ones.
-F - force file system sync even if errors.
If source used > destination space error, do the sync anyway.
If a source partition mount error, skip it and do other syncs.
-x - use set -x for very verbose bash shell script debugging
-V - print rpi-clone version.
Clone a booted file system to a destination disk which is bootable.
The destination disk is a SD card (USB card reader) or USB disk 'sdN' plugged
into a USB port. The 'sdN' name should be a full disk name like sda and not
a partition name like sda1. $PGM works on a Raspberry Pi and can work on
other systems. For a destination disk that shows up as sda, run:
$ sudo rpi-clone sda
Clones can be from a booted SD card or USB disk. For a description, example
clone runs and example usage of above options, see the README.md at:
https://github.com/billw2/rpi-clone
A line logging a $PGM run is written to $clone_log.
Download:
git clone https://github.com/billw2/rpi-clone
"
exit 1
}
readable()
{
val=$1
if [ "$val" == "" ]
then
result=" ??"
else
blk_size=$2
val=$((val / 1000 * blk_size))
if ((val < 1000000))
then
result=$(echo $val \
| awk '{ byte =$1 /1000; printf "%.1f%s", byte, "MB" }')
elif ((val < 1000000000))
then
result=$(echo $val \
| awk '{ byte =$1 /1000/1000; printf "%.1f%s", byte, "GB" }')
else
result=$(echo $val \
| awk '{ byte =$1 /1000/1000/1000; printf "%.1f%s", byte, "TB" }')
fi
fi
printf -v "${3}" "%s" "$result"
}
qecho()
{
if ((!quiet))
then
echo "$@"
fi
}
qprintf()
{
if ((!quiet))
then
printf "$@"
fi
}
unmount_or_abort()
{
if [ "$1" == "" ]
then
return
fi
qprintf "\n $2\n The clone cannot proceed unless it is unmounted."
if confirm "Do you want to unmount $1?" "abort"
then
if ! umount $1
then
echo "$PGM could not unmount $1."
echo -e "Aborting!\n"
exit 0
fi
fi
}
unmount_list()
{
if [ "$1" == "" ]
then
return
fi
for dir in $1
do
qecho " unmounting $dir"
if ! umount $dir
then
qecho " Failed to unmount: $dir"
fi
done
}
mount_partition()
{
qecho " Mounting $1 on $2"
if ! mount $1 $2
then
echo " Mount failure of $1 on $2."
if [ "$3" != "" ]
then
unmount_list $3
fi
echo "Aborting!"
exit 1
fi
}
rsync_file_system()
{
src_dir="$1"
dst_dir="$2"
qprintf " => rsync $1 $2 $3 ..."
if [ "$3" == "with-root-excludes" ]
then
rsync $rsync_options --delete \
$exclude_swapfile \
--exclude '.gvfs' \
--exclude '/dev/*' \
--exclude '/mnt/clone/*' \
--exclude '/proc/*' \
--exclude '/run/*' \
--exclude '/sys/*' \
--exclude '/tmp/*' \
--exclude 'lost\+found/*' \
$src_dir \
$dst_dir
else
rsync $rsync_options --delete \
--exclude '.gvfs' \
--exclude 'lost\+found/*' \
$src_dir \
$dst_dir
fi
qecho ""
}
print_partitions()
{
if ((quiet)) && ((!initialize))
then
return
fi
n_parts=$(( (n_src_parts >= n_dst_parts) ? n_src_parts : n_dst_parts ))
readable $src_disk_size "512" src_size_readable
readable $dst_disk_size "512" dst_size_readable
printf "\n%-43s%s" "Booted disk: $src_disk $src_size_readable" \
"Destination disk: $dst_disk $dst_size_readable"
echo $"
---------------------------------------------------------------------------"
out=$'Part, Size,FS,Label ,Part, Size,FS,Label\n'
for ((p = 1; p <= n_parts; p++))
do
if ((p <= n_src_parts && src_exists[p]))
then
readable ${src_size_sectors[p]} "512" tmp
printf -v sectors_readable "%7s" $tmp
pname="$p ${src_name[p]}"
out=${out}$"$pname,$sectors_readable,${src_fs_type[p]},${src_label[p]},"
else
out=${out}$" , , , ,"
fi
if ((p <= n_dst_parts && dst_exists[p]))
then
readable ${dst_size_sectors[p]} "512" tmp
printf -v sectors_readable "%7s" $tmp
out=${out}$"$p,$sectors_readable,${dst_fs_type[p]},${dst_label[p]},"
else
out=${out}$" , , , ,"
fi
out=${out}$'\n'
done
echo $"$out" | column -t -s ','
if ((alt_root_part_num > 0))
then
echo $"
** Assuming destination root partition for the clone is $dst_part_base$root_part_num
The root FS mount is not from booted $src_disk. It is ${src_root_dev#/dev/}"
fi
echo $"---------------------------------------------------------------------------"
}
print_sync_actions()
{
if ((quiet))
then
return
fi
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
if ((p == root_part_num && alt_root_part_num > 0))
then
part=${src_root_dev#/dev/}
flow="$part to $dst_part_base$p"
else
flow="to $dst_part_base$p"
fi
if ((src_sync_part[p]))
then
readable ${src_used_sectors[p]} "512" used
readable ${dst_size_sectors[p]} "512" size
printf "%-22s%-14s : SYNC %s\n" \
"${src_mounted_dir[p]}" "(${used} used)" \
"$flow (${size} size)"
fi
done
}
print_image_actions()
{
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
pname="$p ${src_name[p]}"
fs_type=${src_fs_type[$p]}
if ((p == root_part_num && alt_root_part_num > 0))
then
part=${src_root_dev#/dev/}
flow="$part to $dst_part_base$p"
else
flow="to $dst_part_base$p"
fi
action=""
if ((p <= n_image_parts))
then
if [ "${src_mounted_dir[p]}" == "/boot" ] && ((p == 1))
then
action="IMAGE $flow FSCK"
elif [ "$fs_type" == "swap" ]
then
action="MKSWAP"
elif ((p != ext_part_num))
then
if [ "${src_mounted_dir[p]}" != "" ] || ((p == n_src_parts))
then
if ((p < n_src_parts || last_part_space || force_sync))
then
action="MKFS SYNC $flow"
else
action="MKFS **NO SYNC**"
fi
else
action="IMAGE $flow"
fi
fi
fi
if ((p == n_image_parts))
then
readable ${src_used_sectors[n_image_parts]} "512" used
printf "%-22s%-14s : RESIZE($image_space_readable) %s\n" \
"$pname" "(${used} used)" "$action"
elif ((src_used_sectors[$p] > 0 && p < n_image_parts))
then
readable ${src_used_sectors[p]} "512" used
printf "%-22s%-14s : $action\n" "$pname" "(${used} used)"
elif [ "$action" != "" ]
then
printf "%-36s : $action\n" "$pname"
fi
done
}
print_options()
{
if ((quiet))
then
return
fi
echo $"---------------------------------------------------------------------------"
if ((force_2_parts))
then
printf "%-22s : %s\n" "-f2" \
"force initialize to first two partitions only"
fi
if [ "$edit_fstab_name" != "" ]
then
printf "%-22s : %s\n" "-e clone fstab edit" \
"edit $src_part_base device entries to $edit_fstab_name"
fi
if [ "$ext_label" != "" ]
then
rep="${ext_label: -1}"
if [ "$rep" == "#" ]
then
msg="all ext partition types"
else
msg="root partition only"
fi
printf "%-22s : %s\n" "-L $ext_label" \
"volume label for $msg."
fi
if ((leave_sd_usb_boot))
then
if ((SD_slot_dst))
then
msg="leave SD card cmdline.txt bootable to USB"
elif ((SD_slot_boot))
then
msg="install boot to USB cmdline.txt on SD card"
else
msg="-l ignored. Src or dst is not a SD card slot."
fi
printf "%-22s : %s\n" "-l SD to USB boot mode" "$msg"
fi
if [ "$setup_args" != "" ]
then
printf "%-22s : %s\n" "Run setup script" "$setup_command $setup_args"
else
printf "%-22s : no\n" "Run setup script"
fi
if ((have_grub))
then
printf "%-22s : %s\n" "Run grub" \
"grub-install --root-directory=$clone /dev/$dst_disk"
fi
printf "%-22s : %s\n" "Verbose mode" "$verbose"
printf "%-23s:\n" "-----------------------"
}
ext_label()
{
pnum=$1
fs_type=$2
flag=$3
label_arg=""
if [ "$ext_label" != "" ] && [[ "$fs_type" == *"ext"* ]]
then
rep="${ext_label: -1}"
if [ "$rep" == "#" ]
then
label_arg=${ext_label:: -1}
label_arg="$flag $label_arg$pnum"
elif ((pnum == root_part_num))
then
label_arg="$flag $ext_label"
fi
fi
printf -v "${4}" "%s" "$label_arg"
}
get_src_disk()
{
partition=${1#/dev/}
disk=${partition:: -1}
num="${partition: -1}"
if [[ $disk == *"mmcblk"* ]]
then
SD_slot_boot=1
disk=${disk:0:7}
src_part_base=${disk}p
fi
printf -v "${2}" "%s" "$disk"
printf -v "${3}" "%s" "$num"
}
# ==== source (booted) disk info and default mount list
#
src_boot_dev=`findmnt /boot -o source -n`
src_root_dev=`findmnt / -o source -n`
SD_slot_boot=0
SD_slot_dst=0
src_part_base=""
boot_part_num=0
alt_root_part_num=0
if [ "$src_boot_dev" == "" ]
then
get_src_disk "$src_root_dev" "src_disk" "unused"
else
get_src_disk "$src_boot_dev" "src_disk" "boot_part_num"
fi
get_src_disk "$src_root_dev" "src_root_disk" "root_part_num"
if [ "$src_disk" == "" ]
then
echo "Cannot find booted device."
exit 1
fi
if [ "$src_part_base" == "" ]
then
src_part_base=$src_disk
fi
if [ "$src_disk" != "$src_root_disk" ]
then
if ((SD_slot_boot))
then
# Handle SD card boots with root on different USB disk device.
# But will assume SD card has a root partition just above its root.
#
alt_root_part_num="$root_part_num"
root_part_num=$((boot_part_num + 1))
else
echo $"
Boot and root are on different disks and it's not a SD card boot.
Don't know how to partition the destination disk!"
exit 1
fi
fi
# src_root_dev, if on device other than booted, is not in src_partition_table
# and src_fdisk_table, but is in src_df_table and src_mount_table
#
src_partition_table=$(parted -m "/dev/$src_disk" unit s print | tr -d ';')
src_fdisk_table=$(fdisk -l /dev/$src_disk | grep "^/dev/")
tmp=$(df | grep -e "^/dev/$src_disk" -e "^/dev/root" -e "$src_root_dev" \
| tr -s " ")
dev=${src_root_dev#/dev/}
src_df_table=$(echo "$tmp" | sed "s/root/$dev/")
n_src_parts=$(echo "$src_partition_table" | tail -n 1 | cut -d ":" -f 1)
src_disk_size=$(echo "$src_partition_table" \
| grep "^/dev/$src_disk" | cut -d ":" -f 2 | tr -d 's')
line=$(fdisk -l /dev/$src_disk | grep "Disk identifier:")
src_disk_ID=${line#*x}
src_mount_table=$(findmnt -o source,target -n -l \
| grep -e "^/dev/$src_disk" -e "^$src_root_dev" | tr -s " ")
n_mounts=$(echo "$src_mount_table" | wc -l)
if ((alt_root_part_num > 0 && n_src_parts < 2))
then
echo $"
Booted disk has only one partition and the root is from another device.
Don't know how to partition the destination disk!
"
exit 1
fi
line=$(echo "$src_fdisk_table" | grep "Extended")
if [ "$line" != "" ]
then
dev=$(echo "$line" | cut -d " " -f 1)
ext_part_num="${dev: -1}"
else
ext_part_num=0
fi
for ((p = 1; p <= n_src_parts; p++))
do
line=$(echo "$src_partition_table" | grep -e "^${p}:")
if [ "$line" == "" ]
then
src_exists[p]=0
continue
fi
src_exists[p]=1
if ((p == root_part_num))
then
src_partition[p]=${src_root_dev#/dev/}
src_device[p]=$src_root_dev
else
src_partition[p]="${src_part_base}${p}"
src_device[p]="/dev/${src_partition[p]}"
fi
# parted sectors are 512 bytes
src_start_sector[p]=$(echo "$line" | cut -d ":" -f 2 | tr -d 's')
src_size_sectors[p]=$(echo "$line" | cut -d ":" -f 4 | tr -d 's')
part_type=$(echo "$line" | cut -d ":" -f 5)
src_mounted_dir[p]=$(echo "$src_mount_table" \
| grep -m 1 -e "^${src_device[p]}" | cut -d " " -f 2)
if [ "${src_mounted_dir[p]}" != "" ]
then
src_sync_part[p]=1
else
src_sync_part[p]=0
fi
src_name[p]=""
if [ "$part_type" != "" ]
then
src_fs_type[p]="$part_type"
else
src_fs_type[p]="--"
fi
src_label[p]="--"
if [ "${src_mounted_dir[p]}" == "/" ]
then
src_name[p]="root"
#
# If root on device other than booted SD card, root_part_num assumed to be
# booted /boot part_num + 1 and alt_root_part_num is from root device.
#
elif ((p == root_part_num)) && ((alt_root_part_num > 0))
then
src_name[p]="root**"
elif ((p == ext_part_num))
then
src_fs_type[p]="EXT"
elif [[ "$part_type" == *"linux-swap"* ]]
then
src_fs_type[p]="swap"
elif [ "${src_mounted_dir[p]}" != "" ]
then
src_name[p]="${src_mounted_dir[p]}"
fi
if [[ "$part_type" == *"ext"* ]]
then
label=`e2label ${src_device[p]} 2> /dev/null`
if [ "$label" != "" ]
then
src_label[p]="$label"
fi
fi
done
# command line
#
setup_args=""
edit_fstab_name=""
ext_label=""
verbose="no"
force_initialize=0
force_2_parts=0
force_sync=0
all_sync=0
usage_error=0
unattended=0
Unattended=0
quiet=0
custom_sync=0
leave_sd_usb_boot=0
convert_to_partuuid=0
while [ "$1" ]
do
case "$1" in
-v|--verbose)
verbose="yes"
rsync_options=${rsync_options}v
;;
-u|--unattended)
unattended=1
;;
-U|--Unattended-init)
unattended=1
Unattended=1
;;
-q|--quiet)
unattended=1
quiet=1
rsync_options=${rsync_options}q
;;
-s|--setup)
shift
if ! command -v $setup_command > /dev/null
then
echo "Cannot find script $setup_command for setup arg \"$1\"."
usage_error=1
fi
if [ "$setup_args" == "" ]
then
setup_args="$1"
else
setup_args="$setup_args $1"
fi
;;
-e|--edit-fstab)
shift
edit_fstab_name=$1
;;
-f|--force-initialize)
force_initialize=1
;;
-f2)
force_initialize=1
force_2_parts=1
;;
-x)
set -x
;;
-a|--all-sync)
all_sync=1
;;
-m|--mountdir)
shift
mount_ok=0
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
if ((!custom_sync)) && ((p != root_part_num))
then
src_sync_part[p]=0
fi
if [ "${src_mounted_dir[p]}" == "$1" ]
then
src_sync_part[p]=1
mount_ok=1
fi
done
if ((!mount_ok))
then
echo "Asking to clone directory \"$1\", but it is not mounted."
usage_error=1
fi
custom_sync=1
;;
-L|--label_partitions)
shift
ext_label=$1
;;
-l|--leave-sd-usb-boot)
leave_sd_usb_boot=1
;;
-F|--Force-sync)
force_sync=1
;;
--convert-fstab-to-partuuid)
convert_to_partuuid=1
;;
-V|--version)
echo $PGM Version: $version
exit 0
;;
-h|--help)
usage
;;
*)
if [ "$dst_disk" != "" ]
then
echo "Bad args"
usage
fi
dst_disk=$1
dir=`expr substr $dst_disk 1 5`
if [ "$dir" == "/dev/" ]
then
dst_disk=${dst_disk#/dev/}
fi
;;
esac
shift
done
if ((custom_sync)) && ((all_sync))
then
echo "-m and -a options at the same time conflict."
exit 1
fi
if ((custom_sync)) && ((force_initialize))
then
echo "-m and -f options at the same time conflict."
exit 1
fi
if [[ "$verbose" == "yes" ]] && ((quiet))
then
echo "-q and -v options at the same time conflict."
exit 1
fi
if ((usage_error))
then
echo ""
exit 1
fi
if ((convert_to_partuuid))
then
unattended=0
Unattended=0
fstab=/etc/fstab
fstab_tmp=/tmp/fstab
fstab_save=${fstab}.${PGM}-save
confirm "This will change your $fstab, are you sure?" "abort"
cp $fstab $fstab_tmp
printf "\nConverting $fstab from device names to PARTUUID\n"
count=0
for ((p = 1; p <= n_src_parts; p++))
do
if grep -q "^/dev/${src_partition[p]}" $fstab_tmp
then
partuuid=$(lsblk -n -o PARTUUID /dev/${src_partition[p]})
sed -i "s/\/dev\/${src_partition[p]}/PARTUUID=$partuuid/" $fstab_tmp
printf " Editing $fstab, changing /dev/${src_partition[p]} to $partuuid\n"
((++count))
fi
done
if ((count))
then
cp $fstab $fstab_save
cp $fstab_tmp $fstab
printf "Your original fstab is backed up to $fstab_save\n"
cmdline_txt=/boot/cmdline.txt
cmdline_save=$cmdline_txt.${PGM}-save
if [ -f $cmdline_txt ] && grep -q "$src_root_dev" $cmdline_txt
then
root_part=${src_partition[root_part_num]}
partuuid=$(lsblk -n -o PARTUUID $src_root_dev)
if [ "$partuuid" != "" ]
then
cp $cmdline_txt $cmdline_save
sed -i "s/\/dev\/$root_part/PARTUUID=$partuuid/" $cmdline_txt
printf " Editing $cmdline_txt, changing root=$src_root_dev to root=PARTUUID=$partuuid\n"
printf "Your original cmdline.txt is backed up to $cmdline_save\n"
fi
fi
else
printf "Could not find any $src_disk partition names in $fstab, nothing changed.\n"
fi
rm $fstab_tmp
echo ""
exit 0
fi
# dst_mount_flag enumerations:
live=1
temp=2
fail=3
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
blocks=0
dst_mount_flag[p]=0
if [ "${src_mounted_dir[p]}" != "" ]
then # df blocks are 1024 bytes
dst_mount_flag[p]=$live
blocks=$(echo "$src_df_table" \
| grep -m 1 "^${src_device[p]}" | cut -d " " -f 3)
# in case intializing, get n_src_parts to compare to dest space
elif ((p == n_src_parts)) \
|| ((all_sync)) \
&& [ "${src_fs_type[p]}" != "EXT" ] \
&& [ "${src_fs_type[p]}" != "swap" ] \
&& [ "${src_fs_type[p]}" != "" ]
then
if mount ${src_device[p]} $clone
then
sleep 1
blocks=$(df | grep "^${src_device[p]}" \
| tr -s " " | cut -d " " -f 3)
umount $clone
dst_mount_flag[p]=$temp
if ((all_sync))
then
src_sync_part[p]=1
fi
else
dst_mount_flag[p]=$fail
fi
fi
src_used_sectors[p]=$((blocks * 2))
done
# ==== destination disk checks
#
if [ "$dst_disk" = "" ]
then
echo "No destination disk given."
usage
fi
chk_disk=`cat /proc/partitions | grep -m 1 $dst_disk`
if [ "$chk_disk" == "" ]
then
echo $"
Cannot find '$dst_disk' in the partition table. The partition table is:"
cat /proc/partitions
exit 1
fi
dst_part_base=$dst_disk
if [[ ${chk_disk: -1} =~ ^[0-9]$ ]]
then
if [[ $dst_disk == *"mmcblk"* ]]
then
SD_slot_dst=1
dst_part_base=${dst_disk}p
if [ "$edit_fstab_name" == "" ] \
&& ! grep -q "^PARTUUID=" /etc/fstab
then
edit_fstab_name=$dst_part_base
assumed_fstab_edit=1
else
assumed_fstab_edit=0
fi
else
qecho $"
Target disk $dst_disk ends with a digit so may be a partition.
$PGM requires disk names like 'sda' and not partition names like 'sda1'."
confirm "Continue anyway?" "abort"
fi
fi
if [ "$src_disk" == "$dst_disk" ]
then
echo "Destination disk $dst_disk is the booted disk. Cannot clone!"
exit 1
fi
dst_partition_table=$(parted -m "/dev/$dst_disk" unit s print | tr -d ';')
n_dst_parts=$(echo "$dst_partition_table" | tail -n 1 | cut -d ":" -f 1)
if [ "$n_dst_parts" == "/dev/$dst_disk" ]
then
n_dst_parts=0
fi
dst_disk_size=$(echo "$dst_partition_table" \
| grep "^/dev/$dst_disk" | cut -d ":" -f 2 | tr -d 's')
dst_root_dev=/dev/${dst_part_base}${root_part_num}
dst_mount_table=$(findmnt -o source,target -n -l \
| grep "^/dev/$dst_disk" | tr -s " ")
dst_fdisk_table=$(fdisk -l /dev/$dst_disk | grep "^/dev/")
line=$(echo "$dst_fdisk_table" | grep "Extended")
if [ "$line" != "" ]
then
dev=$(echo "$line" | cut -d " " -f 1)
ext_num="${dev: -1}"
else
ext_num=0
fi
for ((p = 1; p <= n_dst_parts; p++))
do
line=$(echo "$dst_partition_table" | grep -e "^${p}:")
if [ "$line" == "" ]
then
dst_exists[p]=0
continue
fi
dst_exists[p]=1
part="${dst_part_base}${p}"
dst_partition[p]="$part"
dst_device[p]="/dev/$part"
dst_start_sector[p]=$(echo "$line" | cut -d ":" -f 2 | tr -d 's')
dst_size_sectors[p]=$(echo "$line" | cut -d ":" -f 4 | tr -d 's')
part_type=$(echo "$line" | cut -d ":" -f 5)
if [ "$part_type" != "" ]
then
dst_fs_type[p]="$part_type"
else
dst_fs_type[p]="--"
fi
dst_label[p]="--"
if [[ "$part_type" == *"linux-swap"* ]]
then
dst_fs_type[p]="swap"
elif [[ "$part_type" == *"ext"* ]]
then
label=`e2label ${dst_device[p]} 2> /dev/null`
if [ "$label" != "" ]
then
dst_label[p]="$label"
fi
elif ((p == ext_num))
then
dst_fs_type[p]="EXT"
fi
done
fs_match=1
root_part_match=0
first_part_mismatch=0
initialize=$((force_initialize))
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
stype=${src_fs_type[p]}
dtype=${dst_fs_type[p]}
if [ "$stype" != "$dtype" ]
then
tmp_match=0
if [[ "$stype" == *"fat"* ]] && [[ "$dtype" == *"fat"* ]]
then
tmp_match=1
elif [[ "$stype" == *"ext"* ]] && [[ "$dtype" == *"ext"* ]]
then
tmp_match=1
fi
if ((tmp_match && p == root_part_num))
then
root_part_match=1
fi
if ((!tmp_match)) && ((src_sync_part[p]))
then
first_part_mismatch=$p
fs_match=0
break
fi
fi
done
if ((!fs_match))
then
initialize=1
fs_match_string="do not match"
else
fs_match_string="OK, they match"
fi
if ((initialize)) && ((quiet))
then
echo "Quiet mode asked for but an initialize is required - can't clone!"
exit 1
fi
for ((p = n_dst_parts; p >= 1; --p))
do
if ((!dst_exists[p]))
then
continue
fi
dir=$(echo "$dst_mount_table" \
| grep -e "^${dst_device[p]}" | cut -d " " -f 2)
unmount_or_abort "$dir" \
"Destination disk partition ${dst_device[p]} is mounted on $dir."
done
mounted_dev=$(findmnt $clone -o source -n)
unmount_or_abort "$mounted_dev" \
"Directory $clone is already mounted with $mounted_dev."
mounted_dev=$(findmnt $clone_src -o source -n)
unmount_or_abort "$mounted_dev" \
"Directory $clone_src is already mounted with $mounted_dev."
mounted_dev=$(findmnt /mnt -o source -n)
unmount_or_abort "$mounted_dev" "$mounted_dev is currently mounted on /mnt."
if [ ! -d $clone ]
then
mkdir $clone
fi
if [ ! -d $clone_src ]
then
mkdir $clone_src
fi
# Do not include a dhpys swapfile in rsync. It regenerates at boot.
#
if [ -f /etc/dphys-swapfile ]
then
swapfile=`cat /etc/dphys-swapfile | grep ^CONF_SWAPFILE | cut -f 2 -d=`
if [ "$swapfile" = "" ]
then
swapfile=/var/swap
fi
exclude_swapfile="--exclude $swapfile"
fi
if ((grub_auto)) && [ -d /boot/grub ] && command -v grub-install
then
have_grub=1
else
have_grub=0
fi
print_partitions
if ((initialize))
then
if ((unattended && !Unattended))
then
echo $"
Unattended -u option not allowed when initializing.
Use -U for unattended even if initializing.
"
exit 1
fi
reason="FS types mismatch"
if ((force_initialize))
then
reason="forced by option"
fi
n_image_parts=$((force_2_parts ? 2 : n_src_parts))
start_sector=${src_start_sector[$n_image_parts]}
last_part_sectors=$((dst_disk_size - start_sector))
last_part_used=${src_used_sectors[$n_image_parts]}
last_part_space=$(( (last_part_sectors > last_part_used) ? 1 : 0 ))
if ((last_part_sectors < 7812))
then
printf "%-22s : %s\n" "** FATAL **" \
"Initialize required - $reason."
printf "%-22s : %s\n" "" \
" But destination is too small. To image partition"
readable $((start_sector + 7812)) "512" min_size
printf "%-22s : %s\n" "" \
" source sizes, minimum destination size is $min_size."
if ((n_src_parts > 2))
then
printf "%-22s : %s\n" " " \
" Options: try a two partition clone with -f2, or"
printf "%-22s : %s\n" " " \
" manually create custom partitions that can work."
fi
printf "%-23s:\n" "-----------------------"
exit 1
fi
readable $((last_part_sectors + 7812)) "512" image_space_readable
echo "== Initialize: IMAGE $src_disk partition table to $dst_disk - $reason =="
print_image_actions
print_options
printf "%-22s : %s\n" "** WARNING **" \
"All destination disk $dst_disk data will be overwritten!"
printf "%-22s : %s\n" "" \
" The partition structure will be imaged from $src_disk."
abort=0
if ((!last_part_space))
then
readable $last_part_used "512" used_readable
printf "%-22s : %s\n" "** WARNING **" \
"Destination last partition resize to $image_space_readable"
printf "%-22s : %s\n" "" \
" is too small to hold source used $used_readable."
if [ "$n_image_parts" == "$root_part_num" ] && ((!force_sync))
then
printf "%-22s : %s\n" "** FATAL **" \
"This is the root partition, so aborting!"
printf "%-22s : %s\n" "" \
" Use -F to override."
abort=1
elif ((!force_sync))
then
printf "%-22s : %s\n" "" \
" The partition SYNC is skipped, use -F to override."
else
printf "%-22s : %s\n" "" \
" ** Syncing anyway as you asked with -F. **"
fi
fi
printf "%-23s:\n" "-----------------------"
if ((abort))
then
exit 1
fi
confirm "Initialize and clone to the destination disk ${dst_disk}?" "abort"
if ((!Unattended)) && [ "$ext_label" == "" ]
then
printf "Optional destination ext type file system label (16 chars max): "
read ext_label
fi
start_time=`date '+%H:%M:%S'`
start_sec=$(date '+%s')
if [ "${src_mounted_dir[1]}" == "/boot" ]
then
image_to_part=2
msg="/boot"
else
image_to_part=1
msg="first"
fi
image_to_sector=${src_start_sector[$image_to_part]}
count=$((image_to_sector / 2 / 1024 + 4)) # in MiB blocks for dd bs=1M
printf "\nInitializing\n"
printf " Imaging past the start of $msg partition $image_to_part.\n"
sync
printf " => dd if=/dev/$src_disk of=/dev/$dst_disk bs=1M count=$count ..."
dd if=/dev/$src_disk of=/dev/$dst_disk bs=1M count=$count &> /tmp/$PGM-output
if [ "$?" != 0 ]
then
printf "\n dd failed. See /tmp/$PGM-output.\n"
printf " Try running $PGM again.\n\n"
exit 1
fi
echo ""
sync
sleep 1
sfd0=$(sfdisk -d /dev/$src_disk)
if ((force_2_parts && (n_src_parts > n_image_parts)))
then
remove_part_start=${src_partition[3]}
sfd0=$(echo "$sfd0" | sed -e "/\/dev\/$remove_part_start/,\$d")
fi
part="${src_part_base}$n_image_parts"
sfd1=$(echo "$sfd0" | sed "\/dev\/$part/s/size=[^,]*,//")
if ((ext_part_num > 0 && !force_2_parts))
then
part="${src_part_base}$ext_part_num"
sfd1=$(echo "$sfd1" | sed "\/dev\/$part/s/size=[^,]*,//")
fi
printf " Resizing last partition to end of disk ..."
for ((x = 0; x < 3; ++x))
do
sleep $((x + 1))
sfdisk --force /dev/$dst_disk &> /tmp/$PGM-output <<< "$sfd1"
if [ "$?" == 0 ]
then
break
fi
if ((x == 2))
then
printf "\n====$PGM\n==orig:\n%s\n\n==edited:\n%s\n" \
"$sfd0" "$sfd1" >> /tmp/$PGM-output
printf "\n Resize failed. See /tmp/$PGM-output.\n"
printf " Try running $PGM again.\n\n"
# Don't let dst disk keep source disk ID. Can lead to remounts.
new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ")
qprintf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk > /dev/null
exit 1
fi
done
printf "\n Resize success.\n"
printf " Changing destination Disk ID ..."
sync
sleep 2
new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ")
qprintf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk > /dev/null
printf "\n Delaying so partprobe can update /dev entries ..."
sync
sleep 2
partprobe "/dev/$dst_disk"
sleep 2
echo ""
for ((p = n_image_parts + 1; p <= n_src_parts; p++))
do
src_sync_part[p]=0
done
for ((p = 1; p <= n_image_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
dst_dev=/dev/${dst_part_base}${p}
fs_type=${src_fs_type[$p]}
if ((p == ext_part_num)) \
|| [ "$fs_type" == "--" ]
then
continue
fi
if [ "${src_mounted_dir[p]}" == "/boot" ] && ((p == 1))
then
# Avoid "Volume not properly unmounted" warnings.
printf " => fsck -p $dst_dev ..."
fsck -p $dst_dev &>> /tmp/$PGM-log
echo ""
ext_label $p $fs_type "" label
if [ "$label" != "" ]
then
e2label $dst_dev $label
fi
else
if [ "$fs_type" == "swap" ]
then
printf " => mkswap $dst_dev\n"
mkswap $dst_dev &>> /tmp/$PGM-log
elif ((p != ext_part_num))
then
if [ "${src_mounted_dir[p]}" != "" ] || ((p == n_image_parts))
then
if [ "$fs_type" == "fat16" ]
then
fs_type="vfat"
elif [ "$fs_type" == "fat32" ]
then
fs_type="vfat -F 32"
fi
ext_label $p $fs_type "-L" label
printf " => mkfs -t $fs_type $label $dst_dev ..."
yes | mkfs -t "$fs_type" $label "$dst_dev" &>> /tmp/$PGM-output
# mkfs -t "$fs_type" "$dst_dev" &>> /tmp/$PGM-output <<< "yes"
echo ""
if ((p == n_image_parts))
then
if ((!last_part_space))
then
src_sync_part[p]=$force_sync
else
src_sync_part[p]=1
fi
fi
else
printf " => dd if=${src_device[$p]} of=$dst_dev bs=1M ..."
dd if=${src_device[$p]} of=$dst_dev bs=1M &>> /tmp/$PGM-output
if [ "$?" != 0 ]
then
printf "\n dd failed. See /tmp/$PGM-output.\n"
else
echo ""
fi
ext_label $p $fs_type "" label
if [ "$label" != "" ]
then
echo " e2label $dst_dev $label"
e2label $dst_dev $label
fi
fi
fi
fi
done
ext_label=""
else
qecho "== SYNC $src_disk file systems to $dst_disk =="
print_sync_actions
print_options
informed=0
space_ok=1
all_sync_mount_ok=1
if ((force_sync))
then
err="WARNING"
else
err="FATAL"
fi
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p] || !dst_exists[p]))
then
continue
fi
if ((${dst_size_sectors[p]} < ${src_used_sectors[p]})) \
&& ((src_sync_part[p]))
then
qprintf "%-22s : %s\n" "** $err **" \
"Partition $p: source used > destination space."
space_ok=$force_sync
informed=1
fi
if ((all_sync && dst_mount_flag[p] == fail))
then
qprintf "%-22s : %s\n" "** $err **" \
"Partition $p: mount failed, cannot sync."
all_sync_mount_ok=$force_sync
informed=1
fi
done
if ((informed))
then
qprintf "%-23s:\n" "-----------------------"
fi
if ((!space_ok || !all_sync_mount_ok))
then
printf "\nAborting!\n"
if ((!space_ok))
then
printf " Use -F to override used > space fail or -m to select mounts to clone.\n"
fi
if ((!all_sync_mount_ok))
then
printf " Use -a -F to sync all except failed mounts.\n"
fi
exit 1
fi
confirm "Ok to proceed with the clone?" "abort"
start_time=`date '+%H:%M:%S'`
start_sec=$(date '+%s')
fi
line=$(fdisk -l /dev/$dst_disk | grep "Disk identifier:")
dst_disk_ID=${line#*x}
if [ "$dst_disk_ID" == "$src_disk_ID" ]
then
qecho "Destination disk has same Disk ID as source, changing it."
new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ")
qprintf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk | grep changed
sync
sleep 2
partprobe "/dev/$dst_disk"
sleep 2
line=$(fdisk -l /dev/$dst_disk | grep "Disk identifier:")
dst_disk_ID=${line#*x}
if [ "$dst_disk_ID" == "$src_disk_ID" ]
then
qecho " Failed to set a new Disk ID."
fi
fi
sync
qprintf "\nSyncing file systems (can take a long time)\n"
sync_msg_done=0
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
if ((src_sync_part[p] && dst_mount_flag[p] == temp))
then
if ((!sync_msg_done))
then
qprintf "Syncing unmounted partitions:\n"
fi
sync_msg_done=1
dst_dev=/dev/${dst_part_base}${p}
fs_type=${src_fs_type[$p]}
ext_label $p $fs_type "" label
if [ "$label" != "" ]
then
qecho " e2label $dst_dev $label"
e2label $dst_dev $label
fi
mount_partition ${src_device[p]} $clone_src ""
mount_partition $dst_dev $clone "$clone_src"
unmount_list="$clone_src $clone"
rsync_file_system "${clone_src}/" "${clone}" ""
unmount_list "$unmount_list"
fi
done
qprintf "Syncing mounted partitions:\n"
fs_type=${src_fs_type[$root_part_num]}
ext_label $root_part_num $fs_type "" label
if [ "$label" != "" ]
then
qecho " e2label $dst_root_dev $label"
e2label $dst_root_dev $label
fi
mount_partition $dst_root_dev $clone ""
unmount_list="$clone"
rsync_file_system "//" "$clone" "with-root-excludes"
for ((p = 1; p <= n_src_parts; p++))
do
if ((!src_exists[p]))
then
continue
fi
if ((p != root_part_num && src_sync_part[p] && dst_mount_flag[p] == live))
then
dst_dir=$clone${src_mounted_dir[p]}
if [ ! -d $dst_dir ]
then
mkdir -p $dst_dir
fi
dst_dev=/dev/${dst_part_base}${p}
fs_type=${src_fs_type[$p]}
ext_label $p $fs_type "" label
if [ "$label" != "" ]
then
qecho " e2label $dst_dev $label"
e2label $dst_dev $label
fi
mount_partition "$dst_dev" "$dst_dir" "$unmount_list"
rsync_file_system "${src_mounted_dir[p]}/" "${dst_dir}" ""
unmount_list="$dst_dir $unmount_list"
fi
done
qecho ""
# Fix PARTUUID or device name references in cmdline.txt and fstab
#
fstab=${clone}/etc/fstab
cmdline_txt=${clone}/boot/cmdline.txt
if [ -f $cmdline_txt ]
then
if ((leave_sd_usb_boot && SD_slot_dst))
then
qecho "Leaving SD to USB boot alone."
cp $cmdline_txt ${clone}/boot/cmdline.boot
cmdline_txt=${clone}/boot/cmdline.boot
fi
if grep -q $src_disk_ID $cmdline_txt
then
qecho "Editing $cmdline_txt PARTUUID to use $dst_disk_ID"
sed -i "s/${src_disk_ID}/${dst_disk_ID}/" "$cmdline_txt"
elif [ "$edit_fstab_name" != "" ] && grep -q ${src_part_base} $cmdline_txt
then
qecho "Editing $cmdline_txt references from $src_part_base to $edit_fstab_name"
sed -i "s/${src_part_base}/$edit_fstab_name/" "$cmdline_txt"
fi
if ((leave_sd_usb_boot && SD_slot_boot))
then
qecho "Copying USB cmdline.txt to SD card to set up USB boot."
cp /boot/cmdline.txt /boot/cmdline.boot
cp $cmdline_txt /boot/cmdline.txt
fi
fi
if grep -q $src_disk_ID $fstab
then
qecho "Editing $fstab PARTUUID to use $dst_disk_ID"
sed -i "s/${src_disk_ID}/${dst_disk_ID}/g" "$fstab"
elif [ "$edit_fstab_name" != "" ] && grep -q ${src_part_base} $fstab
then
qecho "Editing $fstab references from $src_part_base to $edit_fstab_name"
sed -i "s/${src_part_base}/${edit_fstab_name}/" "$fstab"
fi
rm -f $clone/etc/udev/rules.d/70-persistent-net.rules
dst_root_vol_name=`e2label $dst_root_dev`
if [ "$dst_root_vol_name" = "" ]
then
dst_root_vol_name="no label"
fi
if ((have_grub))
then
qecho "grub-install --root-directory=$clone /dev/$dst_disk"
if ((quiet))
then
grub-install --root-directory=$clone /dev/$dst_disk &> /dev/null
else
grub-install --root-directory=$clone /dev/$dst_disk
fi
fi
if [ "$setup_args" != "" ]
then
qprintf "\nRunning setup script: $setup_command $setup_args\n"
$setup_command $setup_args
fi
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)" \
>> ${clone}${clone_log}
stop_sec=$(date '+%s')
clone_sec=$((stop_sec - start_sec))
stop_time=`date '+%H:%M:%S'`
qecho "==============================="
qecho "Done with clone to /dev/$dst_disk"
qprintf " Start - %s End - %s Elapsed Time - %d:%02d\n" \
"$start_time" "$stop_time" "$((clone_sec / 60))" "$((clone_sec % 60))"
if ((!unattended))
then
echo -n $"
Cloned partitions are mounted on $clone for inspection or customizing.
Hit Enter when ready to unmount the /dev/$dst_disk partitions ..."
read resp
fi
unmount_list "$unmount_list"
qprintf "===============================\n\n"
exit 0