From 2e7ea43d1e1610587c8a5b107b417b6bf9b8bafd Mon Sep 17 00:00:00 2001 From: Bill Wilson Date: Fri, 15 Sep 2017 18:06:48 -0500 Subject: [PATCH] New Version 2.0 - a complete rewrite. The examples in README.md show the new capabilities. --- README.md | 468 +++++++++++-- rpi-clone | 1710 +++++++++++++++++++++++++++++++++-------------- rpi-clone-setup | 144 ++++ 3 files changed, 1742 insertions(+), 580 deletions(-) create mode 100755 rpi-clone-setup diff --git a/README.md b/README.md index 0d52ab1..4576f0c 100644 --- a/README.md +++ b/README.md @@ -1,89 +1,411 @@ +## rpi-clone -rpi-clone is a shell script that will back up (clone using dd and rsync) -a running Raspberry Pi file system to a destination SD card (via a USB card -reader) or USB disk 'sdN' plugged into a Pi USB port +Version 2 is a complete rewrite with much improved capability over +the original. See the examples below. -I use it to maintain backups of several Pi SD cards I have and the destination -backup SD cards/USB disks can be a different size (smaller or larger) than -the booted SD card or USB disk. rpi-clone works on Raspberry Pi distributions -which have a VFAT boot partition 1 and a Linux root partition 2. Tested on -Raspbian but should work on other distributions which have this same two -partition structure. rpi-clone does not work with NOOBS. +rpi-clone is a shell script that is for cloning a running +Raspberry Pi booted source disk (SD card or USB disk) to a destination +disk which will be bootable. Destination disks are SD cards in a +USB card reader, USB flash disks, or USB hard drives. -rpi-clone can clone the running system to a new SD card/USB disk or can -incrementally rsync to existing backup Raspberry Pi disks. During the clone -to new disks, rpi-clone gives you the opportunity to give a label name to the -partition 2 so you can keep track of which disks have been backed up. -Just stick a correspondingly named sticky label on each disk you have -and you can look up the last clone date for that backup in the rpi-clone log -file /var/log/rpi-clone. My convention is to name 8GB SD cards: - SD-RPI-8A, SD-RPI-8B, ... -and similarly, 4GB cards: - SD-RPI-4A, ... +rpi-clone may work in SD card booted devices other than a +Raspberry Pi because when initializing a disk, rpi-clone images a +first /boot partition and boot loader setup can be captured. +But this will depend on how the boot loading is handled on each device. -If the destination disk has an existing partition 1 and partition 2 -matching the running partition types, rpi-clone assumes (unless using the --f option) that the destination is an existing backup with the partitions -properly sized and set up for a Raspberry Pi. All that is needed -is to mount the partitions and rsync them to the running system. +I also am now using rpi-clone on my Debian desktop, but there are too many +variables in how a /etc/fstab can be set up and a desktop bootloader like +grub can be configured for this to be an officially supported way of +using rpi-clone. -If these partitions are not found (or -f), then rpi-clone will ask -if it is OK to initialize the destination disk partitions. -This is done by a partial 'dd' from the running booted device /dev/mmcblk0 -(or /dev/sd? for a USB boot) to the destination disk /dev/sdN followed by a -fdisk resize and mkfs.ext4 of /dev/sdN partition 2. This creates a completed -partition 1 containing all boot files and an empty but properly sized -partition 2 rootfs. The destination partitions are then mounted on /mnt/clone -and rsynced to the running system. +#### Clone by initialization +Source disk mounted partition file system types are compared to +corresponding destination disk partitions. +If the types are not compatible, then the clone is an +initialization. First, the destination partition structure is +initialized to match the source disk. This is is a convenience that gets +the destination disk partitioned so you can avoid manual partitioning. +All partitions are then cloned either +by imaging source unmounted partitions to corresponding destination +partitions or by doing a destination mkfs followed by a file system +sync of source mounted partitions to the destination partitions. +So to avoid file system inconsistencies, live partitions are synced +and not imaged with one exception. If the first partition +is the /boot partition, it is imaged so that bootloader install state +can be preserved. This is not an issue on a Pi where the GPU knows how +to boot, but could be on other systems that have a bootloader install. +A mounted /boot is rarely active so its file system +state should be consistent, just don't be doing anything to modify your +boot configuration when running rpi-clone. -You should avoid running other disk writing programs when running rpi-clone, -but I find rpi-clone works fine when I run it from a terminal window. -However I usually do quit my browser first because a browser can be -writing many temporary files. +#### Clone by syncing +If the file system types +are compatible, the destination partitions will be mounted and the clone +is a sync of modified files from source to destination. After an +initialize clone, subsequent clones will be syncs. You can skip +the initialize clone and go straight to a sync clone +if a destination disk is manually partitioned and file +systems created (mkfs) that match the mounted source partitions. In +this case a destination disk does not need all partitions to match, only +the mounted ones. Doing this you can have special case use of partitions on +different systems. See my Pi3 example below. -rpi-clone must be run as root and you must have the rsync program installed. -rpi-clone will not cross filesystem boundaries by default - this is normally -desirable. If you wish to include your mounted drive(s) in the clone, -use the -c switch. But use this option with caution since any disk mounted -under /mnt or /media will be included in the clone. - -After rpi-clone is finished with the clone it pauses and asks for confirmation -before unmounting the cloned to SD card/USB disk. This is so you can go look at -the clone results or make any custom final adjustments if needed. For example, -I have a couple of Raspberry Pis and I use one as a master. When I clone for -the benefit of the second Pi, I do a "cd /mnt/clone/etc" and fix the files -needed to customize for the second Pi (well, actually I do that with a -script that takes my desired Pi hostname as an argument). Either way, you -typically might need to change at least these files: - - /etc/hostname # I have one of rpi0, rpi0, ... - /etc/hosts # The localhost line should probably be changed - /etc/network/interfaces # If you need to set up a static IP or alias - -If you cd into the /mnt/clone/tree to make some of these customizations -or just to look around, don't forget to cd out of the /mnt/clone tree -before telling rpi-clone to unmount. - -rpi-clone is on github, to get it and install it to /usr/local/sbin: -Go to https://github.com/billw2/rpi-clone and download the zip file: - - $ unzip rpi-clone-master.zip - $ cd rpi-clone-master - $ cp rpi-clone /usr/local/sbin - -or, use git to clone the repository: +## Install +rpi-clone is on github and is downloaded by cloning the repository. +It is a standalone script and the install is a simple copy to a +bin directory. When run it checks its program dependencies and offers to +install needed packages. But currently rpi-clone knows how to install +only Debian packages with apt-get. +#### On a Raspberry Pi: +``` $ git clone https://github.com/billw2/rpi-clone.git $ cd rpi-clone - $ cp rpi-clone /usr/local/sbin + $ sudo cp rpi-clone rpi-clone-setup /usr/local/sbin +``` +Run rpi-clone or rpi-clone-setup with no args to print usage. -For a French translation of rpi-clone by Mehdi HAMIDA, go to: - https://github.com/idem2lyon/rpi-clone +rpi-clone-setup is for setting the hostname in /etc/hostname and /etc/hosts +files. It is run automatically by rpi-clone if -s args are given, +but before your first clone using a -s option, test run rpi-clone-setup with: +``` + $ sudo rpi-clone-setup -t testhostname +``` +And check the files under /tmp/clone-test to be sure the files have been +edited correctly. If you need additional customizations to a clone, +add them to the rpi-clone-setup script. -GTR2Fan on the Pi forums has instructions for putting rpi-clone into -the menu of the desktop GUI: - https://www.raspberrypi.org/forums/viewtopic.php?f=29&t=137693&p=914109#p914109 +#### On other OS: + To install on another OS, rpi-clone may be renamed to suit. For example, + on my Debian desktop I rename: +``` + $ git clone https://github.com/billw2/rpi-clone.git + $ cd rpi-clone + $ sudo cp rpi-clone /usr/local/sbin/sys-clone + $ sudo cp rpi-clone-setup /usr/local/sbin/sys-clone-setup +``` + +If your other OS is a SD card booted system, it will possibly work. +However it currently does not work for emmc booted devices. +rpi-clone does not directly support usage on a desktop OS. +However, I do use it with my Debian desktop because my setup script +handles my /etc/grub.d/ custom menus and fstab, and the script runs +grub_install. rpi-clone does handle editing of +PARTUUID values in /etc/fstab, but a customized setup script for +a desktop might need to handle file system UUID values or device +name editing in /etc/fstab and the bootloader config. + + +## Usage +See the examples below. To get a usage screen showing available options, +run rpi-clone without any arguments: +``` +pi@rpi0: $ sudo ./rpi-clone +No destination disk given. + +usage: rpi-clone sdN {-v|--verbose} {-f|--force-initialize} {-u|--unattended} + {-U|--Unattended} {-s|--setup} {-m|--mountdir dir } + {-a|--all-sync} {-F|--Force-sync} {-x} {-V|--version} + + -v - list all files as they are copied. + -f - force initialize the destination disk by imaging the booted disk. + -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. + -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. + -m dir - Add dir to a custom list of mounted directories to sync. The + root directory is always synced. NA when initializing. + -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. +``` + +## Raspberry Pi SD Card Booted Examples +#### First clone to a new SD card +The destination SD card is in a USB card reader which when plugged in to +my Pi shows up as sdb because I have another USB disk sda plugged in. +Look in /proc/partitions to see where yours is. The SD card does not +have matching partition types, so the clone is an initialize where +the source partition structure is cloned to the destination. Because +the destination is smaller, the last partition will be resized down. +When disks are initialized a label can be given for the +destination a root file system. I do that so I can keep track of +my cloned cards. When you run rpi-clone, it tells you what it will do: +``` +pi@rpi0: $ sudo ./rpi-clone sdb + +Booted disk: mmcblk0 16.0GB Destination disk: sdb 8.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 58.4MB fat16 -- 1 8.0GB fat32 -- +2 root 16.0GB ext4 SD-RPI-s1 +--------------------------------------------------------------------------- +== Initialize: IMAGE mmcblk0 partition table to sdb - FS types mismatch == +1 /boot (22.5MB used) : IMAGE to sdb1 FSCK +2 root (6.0GB used) : RESIZE(8.0GB) MKFS SYNC to sdb2 +--------------------------------------------------------------------------- +Run setup script : no +Verbose mode : no +-----------------------: +** WARNING ** : All destination disk sdb data will be overwritten! + : The partition structure will be imaged from mmcblk0. +-----------------------: + +Initialize and clone to the destination disk sdb? (yes/no): yes +Optional destination rootfs /dev/sdb2 label (16 chars max): SD-RPI-8a +... +``` + +#### Subsequent clone to the same card +This is a pure sync clone because now the SD card has matching partition +and file system types. Only modified files will be copied from the +source disk to the destination. Also, now I want to setup the clone to +have another hostname to use on another Pi, so I give the -s option. +The rpi-clone-setup script will be called and it will edit /etc/hosts +and /etc/hostname. +``` +pi@rpi0: $ sudo ./rpi-clone sdb -s rpi2 + +Booted disk: mmcblk0 16.0GB Destination disk: sdb 8.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 58.4MB fat16 -- 1 58.4MB fat16 -- +2 root 16.0GB ext4 SD-RPI-s1 2 8.0GB ext4 SD-RPI-8a +--------------------------------------------------------------------------- +== SYNC mmcblk0 file systems to sdb == +/boot (22.5MB used) : SYNC to sdb1 (58.4MB size) +/ (6.0GB used) : SYNC to sdb2 (8.0GB size) +--------------------------------------------------------------------------- +Run setup script : rpi-clone-setup rpi2 +Verbose mode : no +-----------------------: + +Ok to proceed with the clone? (yes/no): +... +``` +#### Clone to manually partitioned hard drive +I wanted to have a Pi3 hard drive USB boot with extra data partitions +and I want to be able to clone back to 2 partition SD cards for use +in other SD card booted Pis. So my USB disk, which was showing up +as sdc, was manually partitioned with partitions of the appropriate +types and file systems made with mkfs. + +Raspbian on the Raspberry Pi needs for the first two partitions to be: +``` + Partition Type File System Type + 1: type c W95 FAT32 (LBA) mkfs -t vfat /dev/sdc1 + 2: type 83 Linux mkfs.ext4 /dev/sdc2 +``` +Although the first partition file system cound be mkfs -t vfat -F 32. +I made the extra partitions mkfs.ext4 and I made a swap partition for +possible later use. Now when rpi-clone is run it will see +that the destination disk has matching types for the booted partitions +1 and 2, so it will do a sync clone without trying to initialize the +destination and my extra partitions 5 and 6 will not be touched. +Here's the first clone attempt to my manually partitioned disk. +The partition types match so rpi-clone goes straight to a sync clone. +Note: when manually preparing like this and you make partition types +match, don't forget that you must also make the file systems. rpi-clone +won't know if you've forgotten that until it tries to mount the +partitions. +``` +pi@rpi0: $ sudo ./rpi-clone sdc + +Booted disk: mmcblk0 16.0GB Destination disk: sdc 160.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 58.4MB fat16 -- 1 104.4MB fat16 -- +2 root 16.0GB ext4 SD-RPI-s1 2 34.4GB ext4 -- + 3 10.7GB swap -- + 4 114.8GB EXT -- + 5 53.7GB ext4 -- + 6 61.1GB ext4 -- +--------------------------------------------------------------------------- +== SYNC mmcblk0 file systems to sdc == +/boot (22.5MB used) : SYNC to sdc1 (104.4MB size) +/ (6.0GB used) : SYNC to sdc2 (34.4GB size) +--------------------------------------------------------------------------- +Run setup script : no +Verbose mode : no +-----------------------: + +Ok to proceed with the clone? (yes/no): +``` + +## Raspberry Pi USB Hard Drive Booted Examples +Now I have booted the USB hard drive I cloned to in the example just +above. I'm going to show several examples here because things now get +interesting with rpi-clone's flexibility. + +#### Routine USB disk clone to 16GB SD card +For this case the Pi is booted and only the /boot partition is mounted. +Nothing much to see here, I'll just type "yes" and only the /boot and +root partitions will be synced. +``` +pi@rpi0: ~$ sudo rpi-clone sda + +Booted disk: sdb 160.0GB Destination disk: sda 16.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 104.4MB fat16 -- 1 58.4MB fat16 -- +2 root 34.4GB ext4 -- 2 16.0GB ext4 SD-RPI-s4 +3 10.7GB swap -- +4 114.8GB EXT -- +5 53.7GB ext4 -- +6 61.1GB ext4 -- +--------------------------------------------------------------------------- +== SYNC sdb file systems to sda == +/boot (21.5MB used) : SYNC to sda1 (58.4MB size) +/ (6.0GB used) : SYNC to sda2 (16.0GB size) +--------------------------------------------------------------------------- +Run setup script : no +Verbose mode : no +-----------------------: + +Ok to proceed with the clone? (yes/no): +``` + +#### USB disk with mounted partition 5 clone to 16GB SD card +Now I have one of my extra partitions mounted and happen to want to +clone to a SD card for another Pi. So here's what I get: +``` +pi@rpi0: ~$ sudo rpi-clone sda + +Booted disk: sdb 160.0GB Destination disk: sda 16.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 104.4MB fat16 -- 1 58.4MB fat16 -- +2 root 34.4GB ext4 -- 2 16.0GB ext4 SD-RPI-s4 +3 10.7GB swap -- +4 114.8GB EXT -- +5 /mnt/mnt 53.7GB ext4 -- +6 61.1GB ext4 -- +--------------------------------------------------------------------------- + +To image the booted disk, the minimum destination disk size is 98.9GB +The destination disk is too small. + +``` +rpi-clone sees the mounted partition 5 and wants to clone it but finds +there's not enough space on the destination drive and won't let me. + +#### USB disk with mounted partition 5 clone to 16GB SD card try 2 +I"ve got things I'm working on and don't want to unmount the partition +to make the clone work, so I use the -m option to tell rpi-clone to +only clone root and /boot and exclude the mounted /mnt/mnt directory: +``` +pi@rpi0: ~$ sudo rpi-clone sda -m /boot + +Booted disk: sdb 160.0GB Destination disk: sda 16.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 104.4MB fat16 -- 1 58.4MB fat16 -- +2 root 34.4GB ext4 -- 2 16.0GB ext4 SD-RPI-s4 +3 10.7GB swap -- +4 114.8GB EXT -- +5 /mnt/mnt 53.7GB ext4 -- +6 61.1GB ext4 -- +--------------------------------------------------------------------------- +== SYNC sdb file systems to sda == +/boot (21.5MB used) : SYNC to sda1 (58.4MB size) +/ (6.0GB used) : SYNC to sda2 (16.0GB size) +--------------------------------------------------------------------------- +Run setup script : no +Verbose mode : no +-----------------------: + +Ok to proceed with the clone? (yes/no): + +``` + +#### USB disk clone to 4GB SD card +I happen to have an old 4GB SD card and here's a try to clone to it: +``` +pi@rpi0: ~$ sudo rpi-clone sda -m /boot + +Booted disk: sdb 160.0GB Destination disk: sda 4.0GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 104.4MB fat16 -- 1 58.4MB fat16 -- +2 root 34.4GB ext4 -- 2 3.9GB ext4 -- +3 10.7GB swap -- +4 114.8GB EXT -- +5 /mnt/mnt 53.7GB ext4 -- +6 61.1GB ext4 -- +--------------------------------------------------------------------------- +== SYNC sdb file systems to sda == +/boot (21.5MB used) : SYNC to sda1 (58.4MB size) +/ (6.0GB used) : SYNC to sda2 (3.9GB size) +--------------------------------------------------------------------------- +Run setup script : no +Verbose mode : no +-----------------------: +** FATAL ** : Partition 2: source used > destination space. +-----------------------: + +Aborting! + Use -F to override used > space fail. + +``` +So even if rpi-clone thinks that the sync won't work because of lack of +space, there is a -F option which will allow the clone to proceed +anyway. Maybe not a good idea, but the interesting thing about +this case is that the sync will actually succeed. That's +because the root used space includes an almost 2GB file system based +swap file (/var/swap) that will be excluded from the sync. + +#### USB disk clone to large USB disk +If you have an extra backup hard drive, you can clone to it and back up +all of your Pi hard drive partitions. For this example I'm +plugging in a drive I happen to use for backing up my desktop, +so the partition types won't match and rpi-clone will want to do an +initialize. The part to note is that rpi-clone will tell you the +steps it will take when doing an image clone of several partitions. +It will even make a swap partition on the destination. +``` +pi@rpi0: ~$ sudo rpi-clone sda + +Booted disk: sdb 160.0GB Destination disk: sda 320.1GB +--------------------------------------------------------------------------- +Part Size FS Label Part Size FS Label +1 /boot 104.4MB fat16 -- 1 52.4GB ext4 -- +2 root 34.4GB ext4 -- 2 52.4GB ext4 gkrellm6-p2 +3 10.7GB swap -- 3 12.6GB swap -- +4 114.8GB EXT -- 4 202.6GB EXT -- +5 /mnt/mnt 53.7GB ext4 -- 5 167.8GB ext4 gkrellm6-p5 +6 61.1GB ext4 -- 6 34.9GB ext4 -- +--------------------------------------------------------------------------- +== Initialize: IMAGE sdb partition table to sda - FS types mismatch == +1 /boot (21.5MB used) : IMAGE to sda1 FSCK +2 root (6.0GB used) : MKFS SYNC to sda2 +3 : MKSWAP +4 : +5 /mnt/mnt (54.3MB used) : MKFS SYNC to sda5 +6 (1.9GB used) : RESIZE(221.2GB) MKFS SYNC to sda6 +--------------------------------------------------------------------------- +Run setup script : no +Verbose mode : no +-----------------------: +** WARNING ** : All destination disk sda data will be overwritten! + : The partition structure will be imaged from sdb. +-----------------------: + +Initialize and clone to the destination disk sda? (yes/no): + +``` +If I do this initialize clone then the next time I clone to the disk +it will be sync clone and will only want to sync whatever partitions +happen to be mounted. But there is a "-a" option to rpi-clone that +will make it clone all partitions even if unmounted. + +## Author Bill Wilson billw--at--gkrellm.net diff --git a/rpi-clone b/rpi-clone index 6d99aa2..4b8ff1d 100755 --- a/rpi-clone +++ b/rpi-clone @@ -1,56 +1,11 @@ #!/bin/bash -VERSION=1.7.1 -# Version 1.7.1 2017/08/19 -# * Bugfix: did not set SRC_ROOT when using -s option -# Version 1.7 2017/08/19 -# * For cloning from USB boots, source disk is booted disk from mtab instead -# of hardwiring mmcblk0. Add -s option to override the auto source disk. -# Version 1.6 2017/05/24 -# * Split rsync into 2 separate processes, 1 for /boot, 1 for rest of filesystem -# * made do not cross filesytem boundaries default behaviour -# * added --cross-filesystems switch to reproduce old behaviour -# * No longer need to manually mkdir optional /mnt directories. -# * No longer need to rsync exclude /mnt and /media -# Version 1.5 2016/09/09 -# * Remove any leading /dev/ from dest disk. -# * Warn dest disk may be a partition if it ends with a digit. -# Version 1.4 2016/03/23 -# * Use -F on mkfs.ext4 and avoid the question. -# Version 1.3 2016/03/16 -# * don't > /dev/null for the mkfs.ext4 since mkfs can ask a question -# Version 1.2 2015/02/17 -# * add -x option -# * tweak some echo messages -# Version 1.1 2015/02/16 -# * Do not include dphys swapfile in the clone. -# * Add a missing -s flag to one of the parted commands. -# * Dump parted stderr to /dev/null since it now complains about destination -# disk partitions ending outside of disk before I get chance to resize -# the partitions. -# * Strip trailing s from PART_START - (fdisk now doesn't seem to accept...) -# +version=2.0.0 PGM=`basename $0` +setup_command="$PGM-setup" -RSYNC_OPTIONS="--force -rltWDEgoptx" - -# Where to mount the disk filesystems to be rsynced. -CLONE=/mnt/clone - -CLONE_LOG=/var/log/$PGM.log - -HOSTNAME=`hostname` - -SRC_BOOT_PARTITION=`fgrep " /boot " /etc/mtab | cut -f 1 -d ' ' ` -SRC_DISK=${SRC_BOOT_PARTITION:5:3} -if [ "$SRC_DISK" == "mmc" ] -then - SRC_DISK=mmcblk0 - SRC_ROOT=mmcblk0p2 -else - SRC_ROOT=${SRC_DISK}2 -fi +rsync_options="--force -rltWDEgoptx" if [ `id -u` != 0 ] then @@ -58,520 +13,1261 @@ then exit 1 fi -if ! rsync --version > /dev/null +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 - echo -e "\nOoops! rpi-clone needs the rsync program but cannot find it." - echo "Make sure rsync is installed:" - echo " $ sudo apt-get update" - echo -e " $ sudo apt-get install rsync\n" - exit 0 + confirm "Do you want to apt-get install the packages?" "abort" + apt-get install -y --no-install-recommends $need_packages fi -if test -e /sbin/fsck.vfat -then - HAVE_FSCK_VFAT="yes" -else +clone=/mnt/clone +clone_src=/mnt/clone-src +clone_log=/var/log/$PGM.log + +HOSTNAME=`hostname` - 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 {-v|--verbose} {-f|--force-initialize} {-s|--src-disk sdS} {-c|--cross-filesystems} {-x}" - echo " -v - list all files as they are copied." - echo " -f - force initialize the destination partitions" - echo " -s sdS - specify sdS is the booted source disk to clone from." - echo " Use this if the boot source auto detect from /etc/mtab fails." - echo " Use with caution, be sure sdS is really the booted disk." - echo " -c - cross filesystem boundaries (include mounted drives)" - echo " -x - use set -x for very verbose bash shell script debugging" - echo "" - echo " Example: $PGM sda" - echo " Clones from booted SD card mmcblk0 or booted USB disk sd? to sda." - echo " The booted disk is automatically determined from /etc/mtab." - echo " Example: $PGM -s sda sdb" - echo " Use sda as the booted source disk to clone from." - echo " Use this if the auto detect of the booted sda disk fails." - echo "" - echo " Clone (rsync) a running Raspberry Pi file system to a destination" - echo " SD card (in a USB card reader) or USB disk 'sdN' plugged into a" - echo " Pi USB port." - echo " $PGM can clone to a new SD card/USB disk which will be initialized" - echo " or can incrementally rsync to previously backed up SD cards/USB disks." - echo "" - echo " If the destination disk 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 destination is an existing backup with the partitions" - echo " properly sized and set up for a Raspberry Pi. All that is needed" - echo " is to mount the partitions and rsync them to the running system." - echo "" - echo " If these partitions are not found (or -f), then $PGM will ask" - echo " if it is OK to initialize the destination disk partitions." - echo " This is done by a partial 'dd' from the booted source disk (currently" - echo " /dev/$SRC_DISK) to the destination SD card /dev/sdN followed" - echo " by a fdisk resize and mkfs.ext4 of /dev/sdN 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 destination partitions are then mounted and rsynced to the" - echo " running system." - echo "" - echo " By default the rsync operation will not cross filesystem boundaries." - echo " Use the -c switch if you wish to include your mounted drive(s)" - echo " Warning: any disk mounted under /mnt or /media will then be included." - echo "" - echo " The destination partitions will be mounted on $CLONE." - echo " A log will be written to $CLONE_LOG." - echo " It's better to avoid running other disk writing programs" - echo " when running $PGM." - echo "" - echo " Version $VERSION" - exit 0 + echo $" +usage: $PGM sdN {-v|--verbose} {-f|--force-initialize} {-u|--unattended} + {-U|--Unattended} {-s|--setup} {-m|--mountdir dir } + {-a|--all-sync} {-F|--Force-sync} {-x} {-V|--version} + + -v - list all files as they are copied. + -f - force initialize the destination disk by imaging the booted disk. + -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. + -s host - add 'host' to args passed to script $setup_command and run it + after cloning but before unmounting partitions. For setting + clone disk hostname, but args can be what the script expects. + -m dir - Add dir to a custom list of mounted directories to sync. The + root directory is always synced. NA when initializing. + -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 $PGM 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 + + $PGM can be from a booted SD card or USB disk. For a complete description + and examples, 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 } -VERBOSE=off -CROSS_FILESYSTEMS=off +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" + } + +unmount_or_abort() + { + if [ "$1" == "" ] + then + return 0 + fi + echo "" + echo " $2" + echo " 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 + return 1 + } + +unmount_list() + { + if [ "$1" == "" ] + then + return 1 + fi + for dir in $1 + do + echo "unmounting: $dir" + if ! umount $dir + then + echo " Failed to unmount: $dir" + fi + done + } + +mount_partition() + { + echo " 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" + + printf " 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 + echo "" + } + +print_partitions() + { + 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_disk$root_part_num + The root FS mount is not from booted $src_disk. It is ${src_root_dev#/dev/}" + + fi + echo $"---------------------------------------------------------------------------" + } + +print_sync_actions() + { + 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_disk$p" + else + flow="to $dst_disk$p" + fi + if ((src_sync_part[p])) + then + readable ${src_used_sectors[p]} "512" used + readable ${dst_size_sectors[p]} "512" size + printf "%-12s%-16s : SYNC %s\n" \ + "${src_mounted_dir[p]}" "(${used} used)" \ + "$flow (${size} size)" + fi + done + } + +print_image_actions() + { + last_sector_size=$1 + + 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_disk$p" + else + flow="to $dst_disk$p" + fi + + action="" + + 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 + + if ((p == n_src_parts)) + then + readable ${src_used_sectors[n_src_parts]} "512" used + printf "%-12s%-16s : RESIZE($last_sector_size) %s\n" \ + "$pname" "(${used} used)" "$action" + elif ((src_used_sectors[$p] > 0)) + then + readable ${src_used_sectors[p]} "512" used + printf "%-12s%-16s : $action\n" "$pname" "(${used} used)" + else + printf "%-28s : $action\n" "$pname" + fi + done + } + +print_options() + { + echo $"---------------------------------------------------------------------------" + if [ "$setup_args" != "" ] + then + printf "%-22s : %s\n" "Run setup script" "$setup_command $setup_args" + else + printf "%-22s : no\n" "Run setup script" + fi + printf "%-22s : %s\n" "Verbose mode" "$verbose" + printf "%-23s:\n" "-----------------------" + } + +get_disk() + { + partition=${1#/dev/} + disk=${partition:: -1} + num="${partition: -1}" + if [[ $disk == *"mmcblk"* ]] + then + SD_card_boot=1 + disk=${disk:0:7} + src_part_p="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_card_boot=0 +src_part_p="" + +boot_part_num=0 +alt_root_part_num=0 + + +if [ "$src_boot_dev" == "" ] +then + get_disk "$src_root_dev" "src_disk" "unused" +else + get_disk "$src_boot_dev" "src_disk" "boot_part_num" +fi + +get_disk "$src_root_dev" "src_root_disk" "root_part_num" + +if [ "$src_disk" == "" ] +then + echo "Cannot find booted device." + exit 1 +fi + +if [ "$src_disk" != "$src_root_disk" ] +then + if ((SD_card_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 -b \ + | 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_disk}${src_part_p}${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="" + +verbose="no" + +force_initialize=0 +force_sync=0 +all_sync=0 +usage_error=0 +unattended=0 +Unattended=0 +custom_sync=0 while [ "$1" ] do case "$1" in -v|--verbose) - VERBOSE=on - RSYNC_OPTIONS=${RSYNC_OPTIONS}v + verbose="yes" + rsync_options=${rsync_options}v + ;; + -u|--unattended) + unattended=1 + ;; + -U|--Unattended-init) + unattended=1 + Unattended=1 + ;; + -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 + setup_args="$setup_args $1" ;; -f|--force-initialize) - FORCE_INITIALIZE=true - ;; - -c|--cross-filesystems) - CROSS_FILESYSTEMS=on - RSYNC_OPTIONS=${RSYNC_OPTIONS/x} - ;; - -s|--src-disk) - shift - SRC_DISK=$1 - if [ "$SRC_DISK" == "mmcblk0" ] - then - SRC_ROOT=${SRC_DISK}p2 - else - SRC_ROOT=${SRC_DISK}2 - fi + force_initialize=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 + ;; + -F|--Force-sync) + force_sync=1 + ;; + -V|--version) + echo $PGM Version: $version + exit 0 + ;; -h|--help) usage ;; *) - if [ "$DST_DISK" != "" ] + if [ "$dst_disk" != "" ] then echo "Bad args" usage fi - DST_DISK=$1 + dst_disk=$1 + dir=`expr substr $dst_disk 1 5` + if [ "$dir" == "/dev/" ] + then + dst_disk=${dst_disk#/dev/} + fi ;; esac shift done -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:` - -#echo $SRC_DISK to $DST_DISK SRC_ROOT: $SRC_ROOT -#echo $SRC_BOOT_PARTITION_TYPE -#echo $SRC_ROOT_PARTITION_TYPE - -if [ "$DST_DISK" = "" ] +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 ((usage_error)) then - echo "No destination disk specified." echo "" - usage - exit 0 + exit 1 fi -# Remove leading /dev/ -DIR=`expr substr $DST_DISK 1 5` -if [ "$DIR" == "/dev/" ] -then - DST_DISK=${DST_DISK#/dev/} -fi +# dst_mount_flag enumerations: +live=1 +temp=2 +fail=3 -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 card reader connected to a" - echo "USB port. 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 - -CHK_DISK=`cat /proc/partitions | grep -m 1 $DST_DISK` -D=${CHK_DISK: -1} -if [ "$D" -eq "$D" ] 2>/dev/null -then - echo " The target disk you entered, $DST_DISK, ends with a digit and this" - echo " may mean you have given a partition name instead of a disk name." - echo " For example, if you want to clone to a disk sda," - echo " specify sda and not one of its partition names like sda1 or sda2." - echo -n "Do you want to continue anyway? (yes/no):" - read resp - if [ "$resp" != "y" ] && [ "$resp" != "yes" ] - then - echo "" - exit 0 - fi -fi - -unmount_or_abort() - { - echo -n "Do you want to unmount $1? (yes/no): " - read resp - 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 - -# Check that none of the destination partitions are busy (mounted). -# -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 - - -TEST_MOUNTED=`fgrep " $CLONE " /etc/mtab | cut -f 1 -d ' ' ` -if [ "$TEST_MOUNTED" != "" ] -then - echo "This script uses $CLONE for mounting filesystems, but" - echo "$CLONE is already mounted with $TEST_MOUNTED." - unmount_or_abort $CLONE -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 is currently mounted on /mnt." - unmount_or_abort /mnt - mkdir $CLONE - fi -fi - - -# Borrowed from do_expand_rootfs in raspi-config -expand_rootfs() - { - # Get the starting offset of the root partition - # (with Jessie's parted, now need to strip trailing 's' from PART_START) - PART_START=$(parted /dev/$SRC_DISK -ms unit s p \ - | grep "^2" | cut -f 2 -d: | cut -f 1 -d s) - [ "$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 </dev/null \ - | grep "^1" | cut -f 5 -d:` -DST_ROOT_PARTITION_TYPE=`parted /dev/$DST_DISK -ms p 2>/dev/null \ - | grep "^2" | cut -f 5 -d:` - -CLONE_MODE="rsync modified files to existing $DST_DISK file systems" - -if [ "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" ] || \ - [ "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" ] || \ - [ "$FORCE_INITIALIZE" = "true" ] -then - CLONE_MODE="rsync all files to $DST_DISK root file system" - echo "" - if [ "$FORCE_INITIALIZE" = "true" ] - then - echo "Forcing a partition initialization of destination disk $DST_DISK" - else - echo "Source disk : $SRC_DISK" - echo "Destination disk: $DST_DISK" - echo " Partition tables do not match, $DST_DISK must be initialized." - fi - - echo "$DST_DISK current partitions are:" - parted /dev/$DST_DISK -s unit MB p 2>/dev/null \ - | sed "/^Model/d ; /^Sector/d ; /^Disk Flags/d" - -# if [ "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" ] -# then -# echo -e " ... Cannot find a destination boot file system of type: $SRC_BOOT_PARTITION_TYPE\n" -# fi -# if [ "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" ] -# then -# echo -e " ... Cannot find a destination root file system of type: $SRC_ROOT_PARTITION_TYPE\n" -# fi - - echo "*** All data on destination disk $DST_DISK will be overwritten! ***" - echo "" - echo -n "Do you want to initialize the destination disk /dev/$DST_DISK? (yes/no): " - - read resp - if [ "$resp" = "y" ] || [ "$resp" = "yes" ] - then - # Image onto the destination disk a beginning fragment of the - # running SD card/USB disk 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 [ "$HAVE_FSCK_VFAT" = "yes" ] - 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 -F $DST_ROOT_PARTITION - - 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 ; /^Disk Flags/d" - - SRC_ROOT_VOL_NAME=`e2label /dev/$SRC_ROOT` - echo "" - echo "Your booted /dev/$SRC_ROOT rootfs existing label: $SRC_ROOT_VOL_NAME" - echo -n "You may enter a label for the destination rootfs $DST_ROOT_PARTITION: " - read resp - 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` - -if [ "$DST_ROOT_VOL_NAME" = "" ] -then - DST_ROOT_VOL_NAME="no label" -fi - -echo "" -echo "======== Clone Summary ========" -echo "Clone mode : $CLONE_MODE" -echo "Clone source disk : $SRC_DISK" -echo "Clone destination disk : $DST_DISK" -echo "Clone destination rootfs : $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on ${CLONE}" -echo "Clone destination bootfs : $DST_BOOT_PARTITION on ${CLONE}/boot" -echo "Verbose mode : $VERBOSE" -echo "Cross filesystems : $CROSS_FILESYSTEMS" -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)?: " -read resp -if [ "$resp" != "y" ] && [ "$resp" != "yes" ] -then - echo -e "Aborting the disk clone.\n" - exit 0 -fi - -# -# =========== End of Setup =========== - - - - -# Mount destination filesystems. - -echo "=> Mounting $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on $CLONE" -if ! mount $DST_ROOT_PARTITION $CLONE -then - echo -e "Mount failure of $DST_ROOT_PARTITION, aborting!\n" - exit 0 -fi - -if [ ! -d $CLONE/boot ] -then - mkdir $CLONE/boot -fi - -echo "=> Mounting $DST_BOOT_PARTITION on $CLONE/boot" -if ! mount $DST_BOOT_PARTITION $CLONE/boot -then - umount $CLONE - echo -e "Mount failure of $DST_BOOT_PARTITION, aborting!\n" - exit 0 -fi - -echo "===============================" - -# Do not include a dhpys swapfile in the clone. dhpys-swapfile will -# regenerate it 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 - -# start sync -# -START_TIME=`date '+%H:%M:%S'` -sync - -# /boot rsync -echo "Starting the /boot rsync to $DST_DISK" -rsync $RSYNC_OPTIONS --delete /boot/ ${CLONE}/boot/ - -# rest of filesystem rsync -# Exclude fuse mountpoint .gvfs, various other mount points, and tmpfs -# file systems from the rsync. -# -echo "Starting the filesystem rsync to $DST_DISK" -echo -n "(This may take several minutes)..." -rsync $RSYNC_OPTIONS --delete \ - $EXCLUDE_SWAPFILE \ - --exclude '.gvfs' \ - --exclude '/dev' \ - --exclude '/mnt/clone' \ - --exclude '/proc' \ - --exclude '/run' \ - --exclude '/sys' \ - --exclude '/tmp' \ - --exclude 'lost\+found' \ - --exclude '/boot' \ - // \ - $CLONE - - - -# Fixup some stuff -# - -for i in dev media mnt proc run sys +for ((p = 1; p <= n_src_parts; p++)) do - if [ ! -d $CLONE/$i ] + if ((!src_exists[p])) then - mkdir $CLONE/$i + 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 + +if [[ ${chk_disk: -1} =~ ^[0-9]$ ]] +then + echo $" + 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 + + +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) +dst_disk_size=$(echo "$dst_partition_table" \ + | grep "^/dev/$dst_disk" | cut -d ":" -f 2 | tr -d 's') +dst_root_dev=/dev/${dst_disk}${root_part_num} + +dst_mount_table=$(findmnt -o source,target -n -l -b \ + | 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_disk}${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 -if [ ! -d $CLONE/tmp ] +fs_match=1 +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)) && ((src_sync_part[p])) + then + fs_match=0 + fi + fi +done + + +if ((!fs_match)) then - mkdir $CLONE/tmp - chmod a+w $CLONE/tmp + initialize=1 + fs_match_string="do not match" +else + fs_match_string="OK, they match" +fi + +for ((p = 1; p <= n_dst_parts; 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 ${dst_mounted_dir[p]}." +done + + +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 + +# TODO: exclude fuse mounts (readlink_stat errors): process findmnt -D | fuse + +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." + +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 + + start_sector=${src_start_sector[$n_src_parts]} + last_part_sectors=$((dst_disk_size - start_sector)) + last_part_used=${src_used_sectors[$n_src_parts]} + last_part_space=$(( (last_part_sectors > last_part_used) ? 1 : 0 )) + + if ((last_part_sectors < 7812)) + then + readable $((start_sector + 7812)) "512" min_size + echo $" +To image the booted disk, the minimum destination disk size is $min_size +The destination disk is too small. +" + exit 1 + fi + + readable $((last_part_sectors + 7812)) "512" space_readable + + echo "== Initialize: IMAGE $src_disk partition table to $dst_disk - $reason ==" + print_image_actions "$space_readable" + 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 $space_readable" + printf "%-22s : %s\n" "" \ + " is too small to hold source used $used_readable." + if [ "$n_src_parts" == "$root_part_num" ] + then + printf "%-22s : %s\n" "" \ + " This is the root partition, so aborting!" + 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" + + root_label="" + if ((!Unattended)) + then + printf "Optional destination rootfs $dst_root_dev label (16 chars max): " + read root_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 + + echo "Imaging past the start of $msg partition $image_to_part." + sync + echo " => 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 + echo "" + sync + + sfd0=$(sfdisk -d /dev/$src_disk) +# part="$dst_disk$n_src_parts" + part="$src_disk$src_part_p$n_src_parts" + sfd1=$(echo "$sfd0" | sed "\/dev\/$part/s/size=[^,]*,//") + + if ((ext_part_num > 0)) + then +# part="$dst_disk$ext_part_num" + part="$src_disk$src_part_p$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-fail <<< "$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-fail + printf "\n Resize failed. See /tmp/$PGM-fail.\n\n" + exit 1 + fi + done + printf "\n Resize success.\n\n" + partprobe "/dev/$dst_disk" + sleep 2 + + for ((p = 1; p <= n_src_parts; p++)) + do + if ((!src_exists[p])) + then + continue + fi + dst_dev=/dev/${dst_disk}${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 "\n => fsck -p $dst_dev ..." + fsck -p $dst_dev &>> /tmp/$PGM-log + echo "" + else + if [ "$fs_type" == "swap" ] + then + printf "\n => mkswap $dst_dev\n" + mkswap $dst_dev + elif ((p != ext_part_num)) + then + if [ "${src_mounted_dir[p]}" != "" ] || ((p == n_src_parts)) + then + if [ "$fs_type" == "fat16" ] + then + fs_type="vfat" + elif [ "$fs_type" == "fat32" ] + then + fs_type="vfat -F 32" + fi + printf "\n => mkfs -t $fs_type $dst_dev\n" + mkfs -t "$fs_type" "$dst_dev" <<< "yes" + echo "" + if ((p == n_src_parts)) + then + if ((!last_part_space)) + then + src_sync_part[p]=$force_sync + else + src_sync_part[p]=1 + fi + fi + else + printf "\n => dd if=${src_device[$p]} of=$dst_dev bs=1M ...\n" + dd if=${src_device[$p]} of=$dst_dev bs=1M + echo "" + fi + fi + fi + done + + if [ "$root_label" != "" ] + then + e2label $dst_root_dev "$root_label" + fi +else + echo "== 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 + printf "%-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 + printf "%-22s : %s\n" "** $err **" \ + "Partition $p: mount failed, cannot sync." + all_sync_mount_ok=$force_sync + informed=1 + fi + done + if ((informed)) + then + printf "%-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.\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 + echo "=> Destination disk has same Disk ID as source, changing it." + new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ") + printf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk | grep changed + sync + sleep 1 + + line=$(fdisk -l /dev/$dst_disk | grep "Disk identifier:") + dst_disk_ID=${line#*x} + if [ "$dst_disk_ID" == "$src_disk_ID" ] + then + echo " Failed to set a new Disk ID." + fi +fi + +sync +printf "\nSyncing file systems (can take a long time):" + +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 + printf "\nSyncing unmounted partitions\n" + fi + sync_msg_done=1 + mount_partition ${src_device[p]} $clone_src "" + mount_partition /dev/$dst_disk$p $clone "$clone_src" + unmount_list="$clone_src $clone" + rsync_file_system "${clone_src}/" "${clone}" "" + unmount_list "$unmount_list" + fi + done + + +printf "\n=> Syncing mounted partitions\n" +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 + mount_partition /dev/$dst_disk$p "$dst_dir" "$unmount_list" + rsync_file_system "${src_mounted_dir[p]}/" "${dst_dir}" "" + unmount_list="$dst_dir $unmount_list" + fi +done + + +# Fix PARTUUID references in fstab and cmdline.txt +# +if grep -q $src_disk_ID ${clone}/etc/fstab +then + echo "=> Editing ${clone}/etc/fstab PARTUUID to use $dst_disk_ID" + sed -i "s/${src_disk_ID}/${dst_disk_ID}/g" "${clone}/etc/fstab" +fi + +if [ -f ${clone}/boot/cmdline.txt ] \ + && grep -q $src_disk_ID ${clone}/boot/cmdline.txt +then + echo "=> Editing ${clone}/boot/cmdline.txt PARTUUID to use $dst_disk_ID" + sed -i "s/${src_disk_ID}/${dst_disk_ID}/" "${clone}/boot/cmdline.txt" fi -rm -f $CLONE/etc/udev/rules.d/70-persistent-net.rules +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 -DATE=`date '+%F %H:%M'` +if [ "$setup_args" != "" ] +then + printf "\n==> Running setup script: $setup_command $setup_args\n" + $setup_command $setup_args +fi -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 +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_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 $CLONE/etc/hostname, $CLONE/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 /dev/$DST_DISK partitions..." -read resp - -echo "unmounting $CLONE/boot" -umount $CLONE/boot - -echo "unmounting $CLONE" -umount $CLONE +stop_sec=$(date '+%s') +clone_sec=$((stop_sec - start_sec)) +stop_time=`date '+%H:%M:%S'` echo "===============================" +echo "Done with clone to /dev/$dst_disk" +printf " 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" +printf "===============================\n\n" exit 0 diff --git a/rpi-clone-setup b/rpi-clone-setup new file mode 100755 index 0000000..5578c30 --- /dev/null +++ b/rpi-clone-setup @@ -0,0 +1,144 @@ +#!/bin/bash + +# Usage: rpi-clone-setup {-t|--test} hostname +# eg: sudo rpi-clone-setup bozo +# +# This script is automatically run by rpi-clone (when it is given -s options) +# to setup an alternate hostname. A cloned file system mounted on /mnt/clone +# is expected unless testing with the -t option. +# +# Or, this script can be run by hand at the end of a clone when rpi-clone +# pauses with the cloned file systems still mounted on /mnt/clone. +# +# Or, run this script by hand with -t to process files in test directories +# under /tmp/clone-test. Run -t and look at the files to see if the files +# have been edited OK. +# eg: sudo rpi-clone-setup -t bozo +# +# This is a starter script that handles only /etc/hosts and /etc/hostname. +# Make sure the script works correctly for your /etc/hosts file. +# +# If adding a customization for another file: +# Add the file to file_list. +# If needed, add a mkdir -p line to the "if ((testing))" part. +# Add the scripting necessary to customize the file. +# Test new scripting by running: rpi-clone-setup -t newhostname +# + +file_list="etc/hostname etc/hosts" + +clone=/mnt/clone +clone_test=/tmp/clone-test + +PGM=`basename $0` + +if [ `id -u` != 0 ] +then + echo "You must be root to run $PGM" + exit 0 +fi + +function usage + { + echo "Usage: $PGM hostname {-t|--test}" + echo " Eg: $PGM rpi1" + echo " Modify files appropriate to set up for a new host." + echo " Files handled are:" + for file in $file_list + do + echo " $file" + done + echo "" + echo "If testing (-t flag) files are copied and processed to $clone_test" + echo "" + exit 0 + } + +testing=0 +while [ "$1" ] +do + case "$1" in + -t|--test) + testing=1 + ;; + *) + if [ "$newhost" != "" ] + then + echo "Bad args" + usage + fi + newhost=$1 + ;; + esac + shift +done + +if [ "$newhost" = "" ] +then + echo -e "You must specify a target hostname\n" + usage +fi + +echo -e "\t$newhost\t- target hostname" + +if ((!testing)) && [ ! -d /mnt/clone/etc ] +then + echo "A destination clone file system is not mounted on /mnt/clone" + echo "Aborting!" + exit 0 +fi + +if ((testing)) +then + cd /tmp + rm -rf $clone_test + clone=$clone_test + + mkdir -p $clone/etc + + echo "**********************************************" + echo "Testing setup: copying files to $clone" + for file in $file_list + do + echo " cp /$file $clone/$file" + cp /$file $clone/$file + done + echo "This test run will modify those files." + echo "**********************************************" + echo "" +fi + + +## +# Set /etc/hostname +# +cd $clone/etc +echo $newhost > hostname +# +# Read it back to verify. +# +echo "$clone/etc/hostname - set new hostname: " +LINE=`cat hostname` +echo -e "$LINE\n" + + +## +# Edit /etc/hosts - edit the sed command if editing fails for your /etc/hosts. +# +cd $clone/etc +sed -i s/"$HOSTNAME"/"$newhost"/ hosts +# +# Read it back to verify. +# +echo "$clone/etc/hosts - set new hostname \"$newhost\" in lines: " +LINE=`grep $newhost hosts` +echo -e "$LINE\n" + + +## +# Add more customizations if needed. +# + + +exit 0 +