├── LICENSE ├── README.md ├── rpi-clone └── rpi-clone-setup /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018-2019, Bill Wilson 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## rpi-clone 2 | Latest version: 2.0.22 3 | 4 | Version 2 is a complete rewrite with improved capability over 5 | the original. See the examples below. 6 | 7 | rpi-clone is a shell script that is for cloning a running 8 | Raspberry Pi booted source disk (SD card or USB disk) to a destination 9 | disk which will be bootable. Destination disks are SD cards in the SD 10 | card slot or a USB card reader, USB flash disks, or USB hard drives. 11 | 12 | I also use rpi-clone on my Debian desktop, but there are too many 13 | variables in how an /etc/fstab can be set up and a desktop bootloader like 14 | grub can be configured for this to be an officially supported way of 15 | using rpi-clone. See On other OS below. 16 | 17 | 18 | #### Clone by initialization 19 | An initialization clone starts by imaging the source disk partition 20 | table to the destination disk This is is a convenience that gets 21 | the destination disk partitioned so you can avoid manual partitioning. 22 | Partitions are then cloned by making destination file systems matching 23 | the source file system types followed by file system syncs to the 24 | destinations. Initialization clones are used when the source file system 25 | types or number of partitions do not match the destination. An initialization 26 | clone can also be forced by command line option. An alternative to 27 | imaging the source partition table is to manually create partitions on 28 | a destination disk with file systems made to match the types in the 29 | source partitions. See example 7 below. 30 | 31 | #### Clone by syncing 32 | If the source and destination disk partition file system types match, 33 | the clone does not start over with a partition table image and making of 34 | filesytems, but instead mounts destination partitions corresponding to the 35 | source and syncs the file systems. After a first clone of a disk, this 36 | operation is an incremental sync which copies only the files that have 37 | changed in the source disk and is much faster than an initilization clone. 38 | 39 | ## Install 40 | rpi-clone is on github and is downloaded by cloning the repository. 41 | It is a standalone script and the install is a simple copy to a 42 | bin directory. When run it checks its program dependencies and offers to 43 | install needed packages. But currently rpi-clone knows how to install 44 | only Debian packages with apt-get. 45 | 46 | #### On a Raspberry Pi: 47 | ``` 48 | $ git clone https://github.com/billw2/rpi-clone.git 49 | $ cd rpi-clone 50 | $ sudo cp rpi-clone rpi-clone-setup /usr/local/sbin 51 | ``` 52 | Make sure /usr/local/sbin is in your $PATH and then run 53 | rpi-clone or rpi-clone-setup with no args to print usage. 54 | 55 | rpi-clone-setup is for setting the hostname in /etc/hostname and /etc/hosts 56 | files. It is run automatically by rpi-clone if -s args are given, 57 | but before your first clone using a -s option, test run rpi-clone-setup with: 58 | ``` 59 | $ sudo rpi-clone-setup -t testhostname 60 | ``` 61 | And check the files under /tmp/clone-test to be sure the files have been 62 | edited correctly. If you need additional customizations to a clone, 63 | add them to the rpi-clone-setup script. 64 | 65 | #### On other OS: 66 | To install on another OS, rpi-clone may be renamed to suit. For example, 67 | on my Debian desktop I rename: 68 | ``` 69 | $ git clone https://github.com/billw2/rpi-clone.git 70 | $ cd rpi-clone 71 | $ sudo cp rpi-clone /usr/local/sbin/sys-clone 72 | $ sudo cp rpi-clone-setup /usr/local/sbin/sys-clone-setup 73 | ``` 74 | On SD card systems other than Raspberry Pi, rpi-clone may work 75 | because an initialize clone images the sectors through the start 76 | of partition 1 to capture the partition table and possible boot loader blocks. 77 | However, unlike the Pi, a bootloader install may need to be run in 78 | the setup script. 79 | As of version 2.0.21 this may be a new requirement for some systems 80 | because rpi-clone no longer images past the end of the first partition. 81 | 82 | rpi-clone does not directly support a traditional desktop OS because there 83 | are different possible bootloaders and while device names or PARTUUID 84 | are handled for /etc/fstab, file system UUIDs are not handled. 85 | However, it works for me and I use rpi-clone renamed as sys-clone on 86 | my Debian desktop because I use PARTUUID in my fstab and I use the grub 87 | bootloader (rpi-clone will run grub-install if it detects it installed 88 | and there is a /boot/grub directory). Using PARTUUID makes fstab 89 | editing simple because only a single number identifies the entire disk. 90 | I do have possible ambiguity in my grub menu setup because I only use 91 | device names in my menu entries while the fstab uses PARTUUID. 92 | But what actually happens is that with a root=/dev/sda2 in my grub menu 93 | default boot line, a USB disk will boot as sda if I have the disk plugged 94 | in when booting, which is what I want. Device names in fstab are a bad 95 | idea when doing this because the USB disk root partition could boot and then 96 | mount my internal drive partitions instead of the USB partitions. But 97 | I use PARTUUID so there will not be cross mounting. And I have a couple 98 | of extra grub menu entries with other root variations just in case. 99 | 100 | 101 | ## Usage 102 | To get a usage screen showing available options, 103 | run rpi-clone without any arguments: 104 | ``` 105 | pi@rpi0: $ sudo rpi-clone 106 | No destination disk given. 107 | 108 | usage: sys-clone sdN {-v|--verbose} {-f|--force-initialize} {-f2} 109 | {-u|--unattended} {-U|--Unattended} {-q|--quiet} 110 | {-s|--setup host} {-e|--edit-fstab sdX } {-m|--mountdir dir } 111 | {-L|--label-partitions label} {-l|--leave-sd-usb-boot} 112 | {-a|--all-sync} {-F|--Force-sync} {-x} {-V|--version} 113 | {--convert-fstab-to-partuuid} 114 | {--exclude=PATTERN} {--exclude-from=FILE} 115 | 116 | -v - verbose rsync, list all files as they are copied. 117 | -f - force initialize the destination disk by imaging the booted disk 118 | partition structure. File systems are then synced or imaged. 119 | -f2 - force initialize only the first 2 partitions to the destination. 120 | So a multi partition USB boot can initialize clone back to 121 | a 2 partition SD card. 122 | -p size - resize destination partition 1 to 'size' bytes. For two partition 123 | initialize (when first clone to blank disk or using -f2 or -f). 124 | Use 'sizeM' for MiB size units. eg -p 256M equals -p 268435456 125 | -u - unattended clone if not initializing. No confirmations asked, 126 | but abort if disk needs initializing or on error. 127 | -U - unattended even if initializing. No confirmations asked, 128 | but abort only on errors. 129 | -q - quiet mode, no output unless errors or initializing. Implies -u. 130 | -s host - add 'host' to args passed to script rpi-clone-setup and run it 131 | after cloning but before unmounting partitions. For setting 132 | clone disk hostname, but args can be what the script expects. 133 | You can give multiple -s arg options. 134 | -e sdX - edit destination fstab to change booted device names to new 135 | device 'sdX'. This is Only for fstabs that use device names. 136 | Used for setting up a USB bootable disk. 137 | -m dir - Add dir to a custom list of mounted directories to sync. Then 138 | the custom list will be synced instead of the default of all 139 | mounted directories. The root directory is always synced. 140 | Not for when initializing. 141 | -L lbl - label for ext type partitions. If 'lbl' ends with '#', replace 142 | the '#' with a partition number and label all ext partitions. 143 | Otherwise apply label to root partition only. 144 | -l - leave SD card to USB boot alone when cloning to SD card mmcblk0 145 | from a USB boot. This preserves a SD card to USB boot setup 146 | by leaving the SD card cmdline.txt using the USB root. When 147 | cloning to USB from SD card this option sets up the SD card 148 | cmdline.txt to boot to the USB disk. 149 | -a - Sync all partitions if types compatible, not just mounted ones. 150 | -F - force file system sync or image for some errors. eg: 151 | If source used > destination space error, do the sync anyway. 152 | If a source partition mount error, skip it and do other syncs. 153 | -x - use set -x for very verbose bash shell script debugging 154 | -V - print rpi-clone version. 155 | ``` 156 | + See examples below for usage of these command line options. 157 | + rpi-clone version 1 briefly had a -s option that is replaced with a 158 | -s option that has different meaning. 159 | + **--convert-fstab-to-partuuid** converts the booted fstab from using device 160 | names to PARTUUID. This is a helper if you wish to convert to PARTUUID as 161 | is standard in recent Raspbian distributions. After running, PARTUUID 162 | usage will propagate to subsequent clones. This changes the booted fstab 163 | and cmdline.txt, so have a backup first. 164 | + FUSE mounts (ssh mounts) should be unmounted before cloning or else the 165 | directory mounted on will not stat and the directory will not be made on the 166 | clone. You will get a readlink stat error from rsync because root can't access 167 | a users FUSE mount - only the user can. 168 | + The examples below show a /boot partition smaller than recommended for the 169 | recent Rasbian Buster release. rpi-clone version 2.0.21 adds the -p option so 170 | the /boot partition can be resized at the same time the root partition is 171 | resized to the end of the disk. If you upgraded Stretch to Buster and are 172 | running with a small /boot, then for the clone to have a resized /boot, run: 173 | ``` 174 | $ rpi-clone -f -p 256M sda 175 | ``` 176 | 177 | 178 | ## rpi-clone Example Runs 179 | #### 0) Examples review - a quick guide to what the examples cover in detail. 180 | 1. Typical two partition clones - SD card or USB disk to USB disk: 181 | ``` 182 | $ rpi-clone sda 183 | ``` 184 | 2. USB boot - clone back to SD card slot: 185 | ``` 186 | $ rpi-clone mmcblk0 187 | ``` 188 | 3. Clone to USB disk intended for use as a standalone Pi3 bootable 189 | disk. No special rpi-clone args are required. 190 | 4. SD card to USB disk clone to create a SD card to USB boot setup: 191 | ``` 192 | If fstab uses PARTUUID: 193 | $ rpi-clone -l sda 194 | If fstab uses device names: 195 | $ rpi-clone -l sda -e sda 196 | ``` 197 | 5. USB boot clone back to SD card slot that preserves SD card to USB boot setup: 198 | ``` 199 | $ rpi-clone -l mmcblk0 200 | ``` 201 | 6. Attempted clone to a disk that is too small. 202 | 7. Manually partition a disk with three partitions so it can 203 | be cloned to from a two partition boot. 204 | 8. Clone from three partition disk to smaller disk large enough 205 | to hold the source three partitions. 206 | ``` 207 | $ rpi-clone sdb 208 | ``` 209 | 9. Clone from three partition disk to smaller disk (sdN or mmcblk0) 210 | not large enough to hold the source three partitions. 211 | A first initialize clone forces a clone of only the first two partitions: 212 | ``` 213 | $ rpi-clone sdb -f2 214 | ``` 215 | 10. Subsequent sync clones from three partition disk to two partition 216 | disk (sdN or mmcblk0): 217 | ``` 218 | If the source third partition is not mounted: 219 | $ rpi-clone sdb 220 | If the source third partition is mounted, select partitions to clone: 221 | $ rpi-clone sdb -m /boot 222 | ``` 223 | **Note** - if a larger USB disk is manually partitioned to create more than 224 | three partitions as in example 7, a smaller disk can be initialize 225 | cloned to only if it is large enough to hold at least part of the last 226 | source partition. 227 | If it is not, then the clone will have to be to a two partition -f2 228 | clone or a clone to a manually partitioned destination. So, for a 229 | multi partition disk, select partition number and sizes with a eye 230 | towards how you will be cloning back to smaller disks. 231 | 11. Desktop demo 232 | 233 | 234 | #### 1) First clone to a new SD card in USB card reader 235 | In this example a new SD card in a USB card reader has been plugged in 236 | that I want to clone to, but it could also be to a USB disk or from a 237 | USB disk back to the SD card slot. 238 | In this case, the disk showed up as sdb because I have another USB 239 | disk sda plugged in. Look in /proc/partitions to see where yours is. 240 | The destination disk does not have partition types matching the booted disk. 241 | + The clone will be an initialize because of partition types mismatch. 242 | + The destination last partition will be resized down in this case because 243 | the destination disk is smaller than the booted disk. 244 | + rpi-clone will ask for a destination root label which I will give 245 | so I can keep track of my clones. 246 | + If PARTUUID is used in fstab and cmdline.txt, those files will be edited 247 | to use the PARTUUID of the destination SD card. The SD card will 248 | bootable when plugged in to the SD card slot. 249 | + If fstab and cmdline.txt use device names (mmcblk0), then rpi-clone 250 | does need to edit the fstab and the card will be bootable when plugged 251 | into a SD card slot. 252 | ``` 253 | pi@rpi0: $ sudo rpi-clone sdb 254 | 255 | Booted disk: mmcblk0 16.0GB Destination disk: sdb 8.0GB 256 | --------------------------------------------------------------------------- 257 | Part Size FS Label Part Size FS Label 258 | 1 /boot 58.4MB fat16 -- 1 8.0GB fat32 -- 259 | 2 root 16.0GB ext4 SD-RPI-s1 260 | --------------------------------------------------------------------------- 261 | == Initialize: IMAGE mmcblk0 partition table to sdb - FS types mismatch == 262 | 1 /boot (22.5MB used) : IMAGE to sdb1 FSCK 263 | 2 root (6.0GB used) : RESIZE(8.0GB) MKFS SYNC to sdb2 264 | --------------------------------------------------------------------------- 265 | Run setup script : no 266 | Verbose mode : no 267 | -----------------------: 268 | ** WARNING ** : All destination disk sdb data will be overwritten! 269 | : The partition structure will be imaged from mmcblk0. 270 | -----------------------: 271 | 272 | Initialize and clone to the destination disk sdb? (yes/no): yes 273 | Optional destination rootfs /dev/sdb2 label (16 chars max): SD-RPI-8a 274 | ... 275 | ``` 276 | 277 | #### 2) Subsequent clone to the same SD card in USB card reader as example 1 278 | This time the destination partition type will match the source booted 279 | types, and I'll add a rpi-clone-setup script -s arg to set a 280 | different destination disk hostname. 281 | 282 | + The clone will be a pure sync where only modified files will be copied. 283 | + The setup script will set the hostnames in the destination disk files 284 | /etc/hostname and /etc/hosts to what I give with -s, in this case rpi2. 285 | ``` 286 | pi@rpi0: $ sudo rpi-clone sdb -s rpi2 287 | 288 | Booted disk: mmcblk0 16.0GB Destination disk: sdb 8.0GB 289 | --------------------------------------------------------------------------- 290 | Part Size FS Label Part Size FS Label 291 | 1 /boot 58.4MB fat16 -- 1 58.4MB fat16 -- 292 | 2 root 16.0GB ext4 SD-RPI-s1 2 8.0GB ext4 SD-RPI-8a 293 | --------------------------------------------------------------------------- 294 | == SYNC mmcblk0 file systems to sdb == 295 | /boot (22.5MB used) : SYNC to sdb1 (58.4MB size) 296 | / (6.0GB used) : SYNC to sdb2 (8.0GB size) 297 | --------------------------------------------------------------------------- 298 | Run setup script : rpi-clone-setup rpi2 299 | Verbose mode : no 300 | -----------------------: 301 | 302 | Ok to proceed with the clone? (yes/no): 303 | ``` 304 | 305 | #### 3) Cloning a Pi3 when fstab uses PARTUUID 306 | If fstab and cmdline.txt use PARTUUID as is the case in recent 307 | Raspbian distributions, rpi-clone always edits** 308 | the destination fstab and cmdline.txt to use the PARTUUID of the 309 | destination disk. So the destination is always bootable. If it 310 | is a USB flash or hard drive it is automatically bootable on a Pi3 311 | as a USB disk so long as the Pi3 has been USB boot enabled with 312 | a program_usb_boot_mode=1 line in /boot/config.txt. 313 | 314 | ** There is one exception. When using the -l option, which is used for 315 | creating or preserving a special SD card to USB boot, the cmdline.txt 316 | on the SD card is not edited after a clone to the SD card, see 317 | examples 4 and 5. 318 | 319 | #### 4) Creating a USB bootable disk for other than a USB enabled Pi3 320 | rpi-clone can be used to create a SD card to USB boot setup and preserve 321 | that setup when cloning from a USB boot back to the SD card slot. 322 | With the SD card booted and a target USB disk plugged in and assuming 323 | the USB disk shows up as sda, the initial clone command depends on 324 | fstab usage of device names or PARTUUID. 325 | 326 | => Before you do this, have a backup of your booted SD card made 327 | as in example 2 without the -l option because these steps will 328 | change the booted SD card cmdline.txt to a USB boot. 329 | 330 | If fstab is using PARTUUID, run: 331 | ``` 332 | $ rpi-clone -l sda 333 | ``` 334 | Or if fstab is using device names, run: 335 | ``` 336 | $ rpi-clone -l -e sda sda 337 | ``` 338 | + Destination disk "sda" will be synced or initialized if required (or add 339 | the -f option to force initialize). 340 | + After files are synced the destination sda fstab and cmdline.txt will 341 | be edited to reference either device names or PARTUUID for the USB disk. 342 | For the fstab uses device names case, the "-e sda" means to edit the 343 | destination /etc/fstab to use "sda" for the root (will be sda1) and 344 | /boot (will be sda2) lines. Also, the destination disk /boot/cmdline.txt 345 | will be edited to use root=/dev/sda2. It is expected that when the USB disk 346 | is plugged in for booting to, it will be sda and this will be a cause 347 | of boot failure if it is not. So using PARTUUID is better because that 348 | will reliably boot. 349 | + The -l option causes the SD card cmdline.txt to be backed up to 350 | cmdline.boot and the destination USB disk cmdline.txt to be copied 351 | to the SD card. Since the USB cmdline.txt was edited to reference 352 | the USB disk, the next Pi boot will start with the SD card 353 | /boot partition, but will redirect to using the USB root partition. 354 | Since the USB fstab was edited to reference the USB disk, the Pi will boot 355 | with the USB partition 1 mounted on /boot. 356 | The SD card /boot partition that initiated the boot process 357 | is no longer in use but can remain in place for subsequent 358 | SD card to USB boots. To make the SD card standalone bootable 359 | again, its cmdline.boot can be moved back to cmdline.txt. 360 | 361 | + If -l is not used, rpi-clone will not replace the currently booted SD card 362 | cmdline.txt and it will need to be edited by hand for the USB boot to work. 363 | 364 | + Also a caution note if fstab uses device names: check your 365 | /boot to be sure it is mounted with /dev/sda1 after booting to USB. 366 | I have a Pi where this fails even though syslog says it mounted. 367 | Just be sure to check when you first do this and before you try example 5. 368 | 369 | Now when the Pi is booted from SD card to USB and the SD card is no longer 370 | in use, the SD card slot is available for cloning to. 371 | 372 | #### 5) Cloning back to SD cards in the SD card slot from USB boots 373 | Whether the boot was a Pi3 straight to USB or a SD card to USB, 374 | the SD card is not in use so it is free to clone back to. This 375 | creates a standalone bootable SD card: 376 | ``` 377 | $ rpi-clone mmcblk0 378 | ``` 379 | However, for the case where the boot was SD card to USB, 380 | this destroys the ability of the SD card to boot to USB. 381 | To preserve that SD to USB boot setup, run: 382 | ``` 383 | $ rpi-clone -l mmcblk0 384 | ``` 385 | + The SD card is cloned to as before. It now has the USB /boot/cmdline.txt. 386 | + But the -l option prevents editing that cmdline.txt to reference the SD card. 387 | It is left alone so that it still references the USB root partition. 388 | So the clone has created USB disk to SD card backup while preserving 389 | the SD card to USB boot setup. On the SD card a backup cmdline.boot 390 | is created and edited to reference the SD card. That backup can be moved 391 | to be cmdline.txt to make the SD card standalone bootable should 392 | you ever want to do that. 393 | Or you could just clone to the SD card without using -l. 394 | + Both above mmcblk0 clone commands apply whether using PARTUUID or 395 | device names. When using device names and cloning to SD cards, 396 | rpi-clone knows fstab device names need editing so "-e mmcblk0p" is assumed. 397 | Now the SD card can be left in permanently and periodically cloned to for 398 | backups and reboots to USB will work as you want. Or other SD 399 | cards can be inserted to create a set of backups. 400 | If making a clone for another Pi that will be SD card bootable, don't use -l. 401 | + Warning: this works if the original SD card to USB boot setup has edited 402 | the USB /etc/fstab to reference USB partitions as is done by rpi-clone 403 | when creating a USB bootable disk with -l. If you have an existing 404 | SD card to USB boot setup where this was not done, then your USB boot 405 | likely has the SD card /boot partition mounted, the SD card is in use 406 | and using rpi-clone for a clone back to the SD card slot will not work. 407 | 408 | 409 | #### 6) Clone to smaller 4GB SD card 410 | I happen to have an old 4GB SD card and here's a try to clone to it: 411 | ``` 412 | root@rpi2: ~$ rpi-clone sda 413 | 414 | Booted disk: mmcblk0 15.8GB Destination disk: sda 4.0GB 415 | --------------------------------------------------------------------------- 416 | Part Size FS Label Part Size FS Label 417 | 1 /boot 58.4MB fat16 -- 1 58.4MB fat16 -- 418 | 2 root 15.8GB ext4 SD-RPI-16N 2 3.9GB ext4 -- 419 | --------------------------------------------------------------------------- 420 | == SYNC mmcblk0 file systems to sda == 421 | /boot (22.5MB used) : SYNC to sda1 (58.4MB size) 422 | / (5.9GB used) : SYNC to sda2 (3.9GB size) 423 | --------------------------------------------------------------------------- 424 | Run setup script : no 425 | Verbose mode : no 426 | -----------------------: 427 | ** FATAL ** : Partition 2: source used > destination space. 428 | -----------------------: 429 | 430 | Aborting! 431 | Use -F to override used > space fail. 432 | ``` 433 | So even if rpi-clone thinks that the sync won't work because of lack of 434 | space, there is a -F option which will allow the clone to proceed 435 | anyway. The interesting thing about this case is that while this might 436 | seem a bad idea, the sync will actually come close to succeeding. That's 437 | because the root used space includes a 1.8GB file system based 438 | swap file (/var/swap) that will be excluded from the sync. If this 439 | clone is forced with -F, the card may boot, but there could be some missing 440 | files if the rsync runs out of space and fails to complete and some things 441 | would not work. 442 | This is just a FYI. 443 | 444 | 445 | #### 7) Clone SD card to USB disk with extra partitions 446 | If you have space with a larger USB disk, you can manually partition it 447 | with extra partitions and clone to it. If partition types and file systems 448 | are made to match the booted SD card, then rpi-clone will sync files and 449 | not try to initialize so the extra destination partitions will not be 450 | touched. 451 | 452 | The requirement to make this work is getting the first two partition types 453 | and file systems right, but the sizes may be different. gparted will make 454 | filesystems automatically but cfdisk or fdisk will not and if 455 | file systems aren't made, rpi-clone will fail to mount the partitions. 456 | 457 | For this example I wanted to partition a 64GB flash disk into 3 partitions 458 | so I could have a large third partition for data. 459 | I made the second root partition 16GB so I could clone this to a 460 | 32GB disk and still have a data partition. 461 | 462 | If using cfdisk or fdisk to make the partitions and then making the file 463 | systems, the work would be: 464 | ``` 465 | Partition Type Size Make File System 466 | 1: type c W95 FAT32 (LBA) 100MiB mkfs -t vfat -F 32 /dev/sda1 467 | 2: type 83 Linux 16GiB mkfs.ext4 /dev/sda2 468 | 3: type 83 Linux rest of disk mkfs.ext4 /dev/sda3 469 | ``` 470 | But what I did was use gparted so the file systems were made for me. 471 | Also, in anticipation of initialize cloning back to SD cards, 472 | I set the first partition start to be 8192 (by setting "Free space preceding" 473 | to 4MiB) to match Raspbian distribution SD card images. 474 | Also I made partition sizes in multiples of 4MiB for SD card compatibility. 475 | 476 | Now I cloned to the 64GB disk. It synced only my two booted partitions 477 | instead of initializing, and it left the third partition alone: 478 | ``` 479 | pi@rpi2: ~$ sudo rpi-clone sda 480 | 481 | Booted disk: mmcblk0 15.8GB Destination disk: sda 64.2GB 482 | --------------------------------------------------------------------------- 483 | Part Size FS Label Part Size FS Label 484 | 1 /boot 58.4MB fat16 -- 1 104.4MB fat32 -- 485 | 2 root 15.8GB ext4 SD-RPI-16N 2 16.8GB ext4 Samsung 64GB A 486 | 3 47.3GB ext4 -- 487 | --------------------------------------------------------------------------- 488 | == SYNC mmcblk0 file systems to sda == 489 | /boot (22.5MB used) : SYNC to sda1 (104.4MB size) 490 | / (5.9GB used) : SYNC to sda2 (16.8GB size) 491 | --------------------------------------------------------------------------- 492 | Run setup script : no 493 | Verbose mode : no 494 | -----------------------: 495 | 496 | Ok to proceed with the clone? (yes/no): 497 | ``` 498 | This was a boot enabled Pi3, so I simply powered down, pulled the SD 499 | card, and rebooted into the three partition USB disk. 500 | 501 | 502 | #### 8) Clone 64GB USB disk with extra partitions to smaller 32GB USB disk 503 | With the USB disk made in example 7 booted and the third partition mounted, 504 | this is a clone to a smaller 32GB USB disk that is still large enough 505 | to hold three partitions. The disk is not manually formatted so it will 506 | be an initialize clone. The space used in the 64GB source third partition 507 | fits into the size of the destination 32GB disk third partition, 508 | so there is no problem: 509 | ``` 510 | pi@rpi2: ~$ sudo rpi-clone sdb 511 | 512 | Booted disk: sda 64.2GB Destination disk: sdb 31.5GB 513 | --------------------------------------------------------------------------- 514 | Part Size FS Label Part Size FS Label 515 | 1 /boot 104.4MB fat32 -- 1 31.5GB fat32 -- 516 | 2 root 16.8GB ext4 Samsung 64GB A 517 | 3 /home/pi/media 47.3GB ext4 -- 518 | --------------------------------------------------------------------------- 519 | == Initialize: IMAGE sda partition table to sdb - FS types mismatch == 520 | 1 /boot (21.5MB used) : IMAGE to sdb1 FSCK 521 | 2 root (5.9GB used) : MKFS SYNC to sdb2 522 | 3 /home/pi/media (54.3MB used) : RESIZE(14.6GB) MKFS SYNC to sdb3 523 | --------------------------------------------------------------------------- 524 | Run setup script : no 525 | Verbose mode : no 526 | -----------------------: 527 | ** WARNING ** : All destination disk sdb data will be overwritten! 528 | : The partition structure will be imaged from sda. 529 | -----------------------: 530 | 531 | Initialize and clone to the destination disk sdb? (yes/no): 532 | ``` 533 | Note that if I had partitioned the 64GB disk with more than three 534 | partitions it would have been more difficult to clone down to the 535 | 32GB card. If there had been 4 partitions, then a smaller disk has 536 | to be large enough to image the sizes of the first three source partitions. 537 | If the disk is too small for that, then an initialize clone would be 538 | limited to a two partition clone as the next example shows. The other 539 | alternative would be a manual partition. The take away is that you need 540 | to consider how you would be cloning to smaller disks when you partition 541 | a larger disk for a Pi. 542 | 543 | #### 9) Clone 64GB USB disk with extra partitions to new 16GB SD card 544 | With a USB boot, the SD card slot is available for use, so I plugged in 545 | a 16GB SD card to clone to: 546 | ``` 547 | pi@rpi2: ~$ sudo rpi-clone mmcblk0 548 | 549 | Booted disk: sda 64.2GB Destination disk: mmcblk0 15.8GB 550 | --------------------------------------------------------------------------- 551 | Part Size FS Label Part Size FS Label 552 | 1 /boot 104.4MB fat32 -- 1 15.8GB fat32 -- 553 | 2 root 16.8GB ext4 Samsung 64GB A 554 | 3 /home/pi/media 47.3GB ext4 -- 555 | --------------------------------------------------------------------------- 556 | Initialize required : partition - types mismatch. 557 | : The minimum destination disk size is 16.9GB 558 | : The destination disk is too small. 559 | : You could try a two partition -f2 clone. 560 | -----------------------: 561 | ``` 562 | This failed because there is a type mismatch that requires an initialize 563 | and additionally the SD card does not have the space for three partitions 564 | given the size of the source disk second partition. The solution is to 565 | tell rpi-clone to clone only the first two partitions and accept that 566 | this backup cannot back up the data partition. The -f2 option is just 567 | for going back to a two partition disk from a multi partitioned disk: 568 | ``` 569 | pi@rpi2: ~$ sudo rpi-clone mmcblk0 -f2 570 | 571 | Booted disk: sda 64.2GB Destination disk: mmcblk0 15.8GB 572 | --------------------------------------------------------------------------- 573 | Part Size FS Label Part Size FS Label 574 | 1 /boot 104.4MB fat32 -- 1 15.8GB fat32 -- 575 | 2 root 16.8GB ext4 Samsung 64GB A 576 | 3 /home/pi/media 47.3GB ext4 -- 577 | --------------------------------------------------------------------------- 578 | == Initialize: IMAGE sda partition table to mmcblk0 - forced by option == 579 | 1 /boot (21.5MB used) : IMAGE to mmcblk0p1 FSCK 580 | 2 root (5.9GB used) : RESIZE(15.7GB) MKFS SYNC to mmcblk0p2 581 | --------------------------------------------------------------------------- 582 | -f2 : force initialize to first two partitions only 583 | Run setup script : no 584 | Verbose mode : no 585 | -----------------------: 586 | ** WARNING ** : All destination disk mmcblk0 data will be overwritten! 587 | : The partition structure will be imaged from sda. 588 | -----------------------: 589 | 590 | Initialize and clone to the destination disk mmcblk0? (yes/no): 591 | ``` 592 | I'm using PARTUUID in /etc/fstab, but if I weren't, this clone would also 593 | automatically edit mmcblk0p names into the destination disk fstab. 594 | 595 | 596 | #### 10) Sync Clone 64GB USB disk with extra partitions to 16GB SD card 597 | With an initialize clone to the SD card done in example 8, I expect 598 | subsequent clones to be sync clones. But I run the rpi-clone command and 599 | I get an error requiring another initialize. This time the 600 | error is because rpi-clone wants to clone the mounted third partition and 601 | there is no destination third partition: 602 | ``` 603 | pi@rpi2: ~$ sudo rpi-clone mmcblk0 604 | 605 | Booted disk: sda 64.2GB Destination disk: mmcblk0 15.8GB 606 | --------------------------------------------------------------------------- 607 | Part Size FS Label Part Size FS Label 608 | 1 /boot 104.4MB fat32 -- 1 104.4MB fat32 -- 609 | 2 root 16.8GB ext4 Samsung 64GB A 2 15.7GB ext4 SD-16c 610 | 3 /home/pi/media 47.3GB ext4 -- 611 | --------------------------------------------------------------------------- 612 | Initialize required : partition 3 /home/pi/media - destination missing. 613 | : Unmount source partitions or use -m 614 | -----------------------: 615 | ``` 616 | But I want to sync and not do another long -f2 initialize, 617 | so there are two choices. 618 | If I unmount the third partition, the clone will sync. If I don't want 619 | to do that, I can tell rpi-clone to sync clone only the /boot partition 620 | (the root partition is included by default): 621 | ``` 622 | pi@rpi2: ~$ sudo rpi-clone mmcblk0 -m /boot 623 | 624 | Booted disk: sda 64.2GB Destination disk: mmcblk0 15.8GB 625 | --------------------------------------------------------------------------- 626 | Part Size FS Label Part Size FS Label 627 | 1 /boot 104.4MB fat32 -- 1 104.4MB fat32 -- 628 | 2 root 16.8GB ext4 Samsung 64GB A 2 15.7GB ext4 SD-16c 629 | 3 /home/pi/media 47.3GB ext4 -- 630 | --------------------------------------------------------------------------- 631 | == SYNC sda file systems to mmcblk0 == 632 | /boot (21.5MB used) : SYNC to mmcblk0p1 (104.4MB size) 633 | / (5.9GB used) : SYNC to mmcblk0p2 (15.7GB size) 634 | --------------------------------------------------------------------------- 635 | Run setup script : no 636 | Verbose mode : no 637 | -----------------------: 638 | 639 | Ok to proceed with the clone? (yes/no): 640 | ``` 641 | 642 | #### 11) Clones from my Debian desktop 643 | Here are a couple of runs to show how rpi-clone looks handling more complex 644 | partitioning on my desktop. I have three primary partitions and extra 645 | extended partitions. I don't have a separate /boot partition but have 646 | a small first partition in case I want to change that. 647 | It probably won't work if there is a primary partition 648 | after an extended partition. 649 | ``` 650 | ~$ sudo sys-clone sdb 651 | /usr/sbin/grub-install 652 | 653 | Booted disk: sda 275.1GB Destination disk: sdb 320.1GB 654 | --------------------------------------------------------------------------- 655 | Part Size FS Label Part Size FS Label 656 | 1 1.0GB ext4 SSD-275-G6-p1 1 320.1GB -- -- 657 | 2 root 52.4GB ext4 SSD-275-G6-p2 658 | 3 12.6GB swap -- 659 | 4 209.0GB EXT -- 660 | 5 /home 62.9GB ext4 SSD-275-G6-p5 661 | 6 /mnt/sda 146.1GB ext4 SSD-275-G6-p6 662 | --------------------------------------------------------------------------- 663 | == Initialize: IMAGE sda partition table to sdb - FS types mismatch == 664 | 1 : IMAGE to sdb1 665 | 2 root (15.3GB used) : MKFS SYNC to sdb2 666 | 3 : MKSWAP 667 | 5 /home (12.9GB used) : MKFS SYNC to sdb5 668 | 6 /mnt/sda (73.1GB used) : RESIZE(191.1GB) MKFS SYNC to sdb6 669 | --------------------------------------------------------------------------- 670 | Run setup script : no 671 | Run grub : grub-install --root-directory=/mnt/clone /dev/sdb 672 | Verbose mode : no 673 | -----------------------: 674 | ** WARNING ** : All destination disk sdb data will be overwritten! 675 | : The partition structure will be imaged from sda. 676 | -----------------------: 677 | 678 | Initialize and clone to the destination disk sdb? (yes/no): 679 | ``` 680 | And a subsequent sync to the same disk after I have manually labeled all 681 | the partitions: 682 | ``` 683 | ~$ sudo sys-clone sdb 684 | /usr/sbin/grub-install 685 | 686 | Booted disk: sda 275.1GB Destination disk: sdb 320.1GB 687 | --------------------------------------------------------------------------- 688 | Part Size FS Label Part Size FS Label 689 | 1 1.0GB ext4 SSD-275-G6-p1 1 1.0GB ext4 Maxone-320A-p1 690 | 2 root 52.4GB ext4 SSD-275-G6-p2 2 52.4GB ext4 Maxone-320A-p2 691 | 3 12.6GB swap -- 3 12.6GB swap -- 692 | 4 209.0GB EXT -- 4 254.0GB EXT -- 693 | 5 /home 62.9GB ext4 SSD-275-G6-p5 5 62.9GB ext4 Maxone-320A-p5 694 | 6 /mnt/sda 146.1GB ext4 SSD-275-G6-p6 6 191.1GB ext4 Maxone-320A-p6 695 | --------------------------------------------------------------------------- 696 | == SYNC sda file systems to sdb == 697 | / (15.3GB used) : SYNC to sdb2 (52.4GB size) 698 | /home (12.9GB used) : SYNC to sdb5 (62.9GB size) 699 | /mnt/sda (73.1GB used) : SYNC to sdb6 (191.1GB size) 700 | --------------------------------------------------------------------------- 701 | Run setup script : no 702 | Run grub : grub-install --root-directory=/mnt/clone /dev/sdb 703 | Verbose mode : no 704 | -----------------------: 705 | 706 | Ok to proceed with the clone? (yes/no): 707 | ``` 708 | 709 | 710 | ## Author 711 | Bill Wilson 712 | billw--at--gkrellm.net 713 | -------------------------------------------------------------------------------- /rpi-clone: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # rpi-clone is Copyright (c) 2018-2019 Bill Wilson 4 | # 5 | # Redistribution and use in source and binary forms, with or without 6 | # modification, are permitted under the conditions of the BSD LICENSE file at 7 | # the rpi-clone github source repository: 8 | # https://github.com/billw2/rpi-clone 9 | 10 | 11 | version=2.0.22 12 | 13 | # setup trusted paths for dependancies (like rsync, grub, fdisk, etc) 14 | export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 15 | 16 | # auto run grub-install if grub detected 17 | grub_auto=1 18 | 19 | PGM=`basename $0` 20 | setup_command="$PGM-setup" 21 | 22 | rsync_options="--force -rltWDEHXAgoptx" 23 | 24 | if [ `id -u` != 0 ] 25 | then 26 | echo -e "$PGM needs to be run as root.\n" 27 | exit 1 28 | fi 29 | 30 | raspbian=0 31 | raspbian_buster=0 32 | if [ -f /etc/os-release ] 33 | then 34 | pretty=`cat /etc/os-release | grep PRETTY` 35 | if [[ "$pretty" == *"Raspbian"* ]] 36 | then 37 | raspbian=1 38 | fi 39 | if ((raspbian)) && [[ "$pretty" == *"buster"* ]] 40 | then 41 | raspbian_buster=1 42 | fi 43 | fi 44 | 45 | confirm() 46 | { 47 | if ((unattended || (initialize && Unattended) )) 48 | then 49 | return 0 50 | fi 51 | printf "\n%s (yes/no): " "$1" 52 | read resp 53 | if [ "$resp" = "y" ] || [ "$resp" = "yes" ] 54 | then 55 | return 0 56 | fi 57 | if [ "$2" == "abort" ] 58 | then 59 | echo -e "Aborting!\n" 60 | exit 0 61 | fi 62 | return 1 63 | } 64 | 65 | # sfdisk is in fdisk package 66 | commands="rsync parted fdisk findmnt column fsck.vfat" 67 | packages="rsync parted util-linux mount bsdmainutils dosfstools" 68 | need_packages="" 69 | 70 | idx=1 71 | for cmd in $commands 72 | do 73 | if ! command -v $cmd > /dev/null 74 | then 75 | pkg=$(echo "$packages" | cut -d " " -f $idx) 76 | printf "%-30s %s\n" "Command not found: $cmd" "Package required: $pkg" 77 | need_packages="$need_packages $pkg" 78 | fi 79 | ((++idx)) 80 | done 81 | 82 | if [ "$need_packages" != "" ] 83 | then 84 | confirm "Do you want to apt-get install the packages?" "abort" 85 | apt-get install -y --no-install-recommends $need_packages 86 | fi 87 | 88 | clone=/mnt/clone 89 | clone_src=/mnt/clone-src 90 | clone_log=/var/log/$PGM.log 91 | 92 | HOSTNAME=`hostname` 93 | 94 | 95 | usage() 96 | { 97 | echo $" 98 | usage: $PGM sdN {-v|--verbose} {-f|--force-initialize} {-f2} 99 | {-p|--p1-size size} {-u|--unattended} {-U|--Unattended} {-q|--quiet} 100 | {-s|--setup host} {-e|--edit-fstab sdX } {-m|--mountdir dir } 101 | {-L|--label-partitions label} {-l|--leave-sd-usb-boot} 102 | {-a|--all-sync} {-F|--Force-sync} {-x} {-V|--version} 103 | {--convert-fstab-to-partuuid} 104 | {--exclude=PATTERN} {--exclude-from=FILE} 105 | 106 | -v - verbose rsync, list all files as they are copied. 107 | -f - force initialize the destination disk by imaging the booted disk 108 | partition structure. File systems are then synced or imaged. 109 | -f2 - force initialize only the first 2 partitions to the destination. 110 | So a multi partition USB boot can initialize clone back to 111 | a 2 partition SD card. 112 | -p size - resize destination partition 1 to 'size' bytes. For two partition 113 | initialize (when first clone to blank disk or using -f2 or -f). 114 | Use 'sizeM' for MiB size units. eg -p 256M equals -p 268435456 115 | -u - unattended clone if not initializing. No confirmations asked, 116 | but abort if disk needs initializing or on error. 117 | -U - unattended even if initializing. No confirmations asked, 118 | but abort only on errors. 119 | -q - quiet mode, no output unless errors or initializing. Implies -u. 120 | -s host - add 'host' to args passed to script rpi-clone-setup and run it 121 | after cloning but before unmounting partitions. For setting 122 | clone disk hostname, but args can be what the script expects. 123 | You can give multiple '-s arg' options. 124 | -e sdX - edit destination fstab to change booted device names to new 125 | device 'sdX'. This is Only for fstabs that use device names. 126 | Used for setting up a USB bootable disk. 127 | -m dir - Add dir to a custom list of mounted directories to sync. Then 128 | the custom list will be synced instead of the default of all 129 | mounted directories. The root directory is always synced. 130 | Not for when initializing. 131 | -L lbl - label for ext type partitions. If 'lbl' ends with '#', replace 132 | '#' with a partition number and label all ext partitions. 133 | Otherwise, apply label to root partition only. 134 | -l - leave SD card to USB boot alone when cloning to SD card mmcblk0 135 | from a USB boot. This preserves a SD card to USB boot setup 136 | by leaving the SD card cmdline.txt using the USB root. When 137 | cloning to USB from SD card this option sets up the SD card 138 | cmdline.txt to boot to the USB disk. 139 | -a - Sync all partitions if types compatible, not just mounted ones. 140 | -F - force file system sync or image for some errors. eg: 141 | If source used > destination space error, do the sync anyway. 142 | If a source partition mount error, skip it and do other syncs. 143 | -x - use set -x for very verbose bash shell script debugging 144 | -V - print rpi-clone version. 145 | 146 | Clone a booted file system to a destination disk which is bootable. 147 | 148 | The destination disk is a SD card (USB card reader) or USB disk 'sdN' plugged 149 | into a USB port. The 'sdN' name should be a full disk name like sda and not 150 | a partition name like sda1. $PGM works on a Raspberry Pi and can work on 151 | other systems. For a destination disk that shows up as sda, run: 152 | 153 | $ sudo rpi-clone sda 154 | 155 | Clones can be from a booted SD card or USB disk. For a description, example 156 | clone runs and example usage of above options, see the README.md at: 157 | 158 | https://github.com/billw2/rpi-clone 159 | 160 | A line logging a $PGM run is written to $clone_log. 161 | 162 | Download: 163 | git clone https://github.com/billw2/rpi-clone 164 | " 165 | exit 1 166 | } 167 | 168 | readable_MiB() 169 | { 170 | val=$1 171 | if [ "$val" == "" ] 172 | then 173 | result=" ??" 174 | else 175 | blk_size=$2 176 | val=$((val / 1024 * blk_size)) 177 | 178 | if ((val < 1024 * 1024)) 179 | then 180 | result=$(echo $val \ 181 | | awk '{ byte =$1 /1024; printf "%.1f%s", byte, "M" }') 182 | elif ((val < 1024 * 1024 * 1024)) 183 | then 184 | result=$(echo $val \ 185 | | awk '{ byte =$1 /1024/1024; printf "%.1f%s", byte, "G" }') 186 | else 187 | result=$(echo $val \ 188 | | awk '{ byte =$1 /1024/1024/1024; printf "%.1f%s", byte, "T" }') 189 | fi 190 | fi 191 | printf -v "${3}" "%s" "$result" 192 | } 193 | 194 | readable_MB() 195 | { 196 | val=$1 197 | if [ "$val" == "" ] 198 | then 199 | result=" ??" 200 | else 201 | blk_size=$2 202 | val=$((val / 1000 * blk_size)) 203 | 204 | if ((val < 1000 * 1000)) 205 | then 206 | result=$(echo $val \ 207 | | awk '{ byte =$1 /1000; printf "%.1f%s", byte, "MB" }') 208 | elif ((val < 1000 * 1000 * 1000)) 209 | then 210 | result=$(echo $val \ 211 | | awk '{ byte =$1 /1000/1000; printf "%.1f%s", byte, "GB" }') 212 | else 213 | result=$(echo $val \ 214 | | awk '{ byte =$1 /1000/1000/1000; printf "%.1f%s", byte, "TB" }') 215 | fi 216 | fi 217 | printf -v "${3}" "%s" "$result" 218 | } 219 | 220 | qecho() 221 | { 222 | if ((!quiet)) 223 | then 224 | echo "$@" 225 | fi 226 | } 227 | 228 | qprintf() 229 | { 230 | if ((!quiet)) 231 | then 232 | printf "$@" 233 | fi 234 | } 235 | 236 | unmount_or_abort() 237 | { 238 | if [ "$1" == "" ] 239 | then 240 | return 241 | fi 242 | qprintf "\n $2\n The clone cannot proceed unless it is unmounted." 243 | 244 | if confirm "Do you want to unmount $1?" "abort" 245 | then 246 | if ! umount $1 247 | then 248 | echo "$PGM could not unmount $1." 249 | echo -e "Aborting!\n" 250 | exit 0 251 | fi 252 | fi 253 | } 254 | 255 | unmount_list() 256 | { 257 | if [ "$1" == "" ] 258 | then 259 | return 260 | fi 261 | for dir in $1 262 | do 263 | qecho " unmounting $dir" 264 | if ! umount $dir 265 | then 266 | qecho " Failed to unmount: $dir" 267 | fi 268 | done 269 | } 270 | 271 | mount_partition() 272 | { 273 | qecho " Mounting $1 on $2" 274 | 275 | if ! mount $1 $2 276 | then 277 | echo " Mount failure of $1 on $2." 278 | if [ "$3" != "" ] 279 | then 280 | unmount_list $3 281 | fi 282 | echo "Aborting!" 283 | exit 1 284 | fi 285 | } 286 | 287 | rsync_file_system() 288 | { 289 | src_dir="$1" 290 | dst_dir="$2" 291 | 292 | qprintf " => rsync $1 $2 $3 ..." 293 | 294 | if [ "$3" == "with-root-excludes" ] 295 | then 296 | rsync $rsync_options --delete \ 297 | $exclude_useropt \ 298 | $exclude_swapfile \ 299 | --exclude '.gvfs' \ 300 | --exclude '/dev/*' \ 301 | --exclude '/mnt/clone/*' \ 302 | --exclude '/proc/*' \ 303 | --exclude '/run/*' \ 304 | --exclude '/sys/*' \ 305 | --exclude '/tmp/*' \ 306 | --exclude 'lost\+found/*' \ 307 | $src_dir \ 308 | $dst_dir 309 | else 310 | rsync $rsync_options --delete \ 311 | $exclude_useropt \ 312 | --exclude '.gvfs' \ 313 | --exclude 'lost\+found/*' \ 314 | $src_dir \ 315 | $dst_dir 316 | fi 317 | qecho "" 318 | } 319 | 320 | print_partitions() 321 | { 322 | if ((quiet)) && ((!initialize)) 323 | then 324 | return 325 | fi 326 | n_parts=$(( (n_src_parts >= n_dst_parts) ? n_src_parts : n_dst_parts )) 327 | 328 | readable_MB $src_disk_size "512" src_size_readable 329 | readable_MB $dst_disk_size "512" dst_size_readable 330 | 331 | printf "\n%-43s%s" "Booted disk: $src_disk $src_size_readable" \ 332 | "Destination disk: $dst_disk $dst_size_readable" 333 | echo $" 334 | ---------------------------------------------------------------------------" 335 | out=$'Part, Size,FS,Label ,Part, Size,FS,Label\n' 336 | for ((p = 1; p <= n_parts; p++)) 337 | do 338 | if ((p <= n_src_parts && src_exists[p])) 339 | then 340 | readable_MiB ${src_size_sectors[p]} "512" tmp 341 | printf -v sectors_readable "%7s" $tmp 342 | pname="$p ${src_name[p]}" 343 | out=${out}$"$pname,$sectors_readable,${src_fs_type[p]},${src_label[p]}," 344 | else 345 | out=${out}$" , , , ," 346 | fi 347 | 348 | if ((p <= n_dst_parts && dst_exists[p])) 349 | then 350 | readable_MiB ${dst_size_sectors[p]} "512" tmp 351 | printf -v sectors_readable "%7s" $tmp 352 | out=${out}$"$p,$sectors_readable,${dst_fs_type[p]},${dst_label[p]}," 353 | else 354 | out=${out}$" , , , ," 355 | fi 356 | out=${out}$'\n' 357 | done 358 | 359 | echo $"$out" | column -t -s ',' 360 | 361 | if ((alt_root_part_num > 0)) 362 | then 363 | echo $" 364 | ** Assuming destination root partition for the clone is $dst_part_base$root_part_num 365 | The root FS mount is not from booted $src_disk. It is ${src_root_dev#/dev/}" 366 | 367 | fi 368 | echo $"---------------------------------------------------------------------------" 369 | } 370 | 371 | print_sync_actions() 372 | { 373 | if ((quiet)) 374 | then 375 | return 376 | fi 377 | for ((p = 1; p <= n_src_parts; p++)) 378 | do 379 | if ((!src_exists[p])) 380 | then 381 | continue 382 | fi 383 | if ((p == root_part_num && alt_root_part_num > 0)) 384 | then 385 | part=${src_root_dev#/dev/} 386 | flow="$part to $dst_part_base$p" 387 | else 388 | flow="to $dst_part_base$p" 389 | fi 390 | if ((src_sync_part[p])) 391 | then 392 | if [ "${src_mounted_dir[p]}" != "" ] 393 | then 394 | src_label="${src_mounted_dir[p]}" 395 | action_label="SYNC" 396 | else 397 | src_label="/dev/${src_partition[p]}" 398 | action_label="MOUNT SYNC" 399 | fi 400 | readable_MiB ${src_used_sectors[p]} "512" used 401 | readable_MiB ${dst_size_sectors[p]} "512" size 402 | printf "%-22s%-14s : %s %s\n" \ 403 | "$src_label" "(${used} used)" "$action_label" \ 404 | "$flow (${size} size)" 405 | fi 406 | done 407 | } 408 | 409 | print_image_actions() 410 | { 411 | for ((p = 1; p <= n_src_parts; p++)) 412 | do 413 | if ((!src_exists[p])) 414 | then 415 | continue 416 | fi 417 | pname="$p ${src_name[p]}" 418 | fs_type=${src_fs_type[$p]} 419 | 420 | if ((p == root_part_num && alt_root_part_num > 0)) 421 | then 422 | part=${src_root_dev#/dev/} 423 | flow="$part to $dst_part_base$p" 424 | else 425 | flow="to $dst_part_base$p" 426 | fi 427 | 428 | action="" 429 | if ((p <= n_image_parts)) 430 | then 431 | if ((p == 1)) 432 | then 433 | if ((p1_size_new > 0)) 434 | then 435 | action="RESIZE MKFS SYNC $flow" 436 | else 437 | action="MKFS SYNC $flow" 438 | fi 439 | elif [ "$fs_type" == "swap" ] 440 | then 441 | action="MKSWAP" 442 | elif ((p != ext_part_num)) 443 | then 444 | if [ "${src_mounted_dir[p]}" != "" ] || ((p == n_src_parts)) 445 | then 446 | if ((p < n_src_parts || last_part_space || force_sync)) 447 | then 448 | action="MKFS SYNC $flow" 449 | else 450 | action="MKFS **NO SYNC**" 451 | fi 452 | else 453 | action="IMAGE $flow" 454 | fi 455 | fi 456 | fi 457 | 458 | if ((p == n_image_parts)) 459 | then 460 | readable_MiB ${src_used_sectors[n_image_parts]} "512" used 461 | printf "%-22s%-14s : RESIZE %s\n" \ 462 | "$pname" "(${used} used)" "$action" 463 | elif ((src_used_sectors[$p] > 0 && p < n_image_parts)) 464 | then 465 | readable_MiB ${src_used_sectors[p]} "512" used 466 | printf "%-22s%-14s : $action\n" "$pname" "(${used} used)" 467 | elif [ "$action" != "" ] 468 | then 469 | printf "%-36s : $action\n" "$pname" 470 | fi 471 | done 472 | } 473 | 474 | print_options() 475 | { 476 | if ((quiet)) 477 | then 478 | return 479 | fi 480 | echo $"---------------------------------------------------------------------------" 481 | 482 | if ((force_sync)) 483 | then 484 | printf "%-22s : %s\n" "-F" \ 485 | "forcing clone to skip some errors." 486 | fi 487 | 488 | if ((force_2_parts)) 489 | then 490 | printf "%-22s : %s\n" "-f2" \ 491 | "force initialize to first two partitions only." 492 | fi 493 | 494 | if ((p1_size_new > 0)) 495 | then 496 | printf "%-3s%-19s : %s %s %s\n" "-p " "$p1_size_arg" \ 497 | "resize /boot to" "$p1_size_new" "blocks of 512 Bytes." 498 | fi 499 | 500 | if [ "$edit_fstab_name" != "" ] 501 | then 502 | printf "%-22s : %s\n" "-e clone fstab edit" \ 503 | "edit $src_part_base device entries to $edit_fstab_name." 504 | fi 505 | 506 | if [ "$ext_label" != "" ] 507 | then 508 | rep="${ext_label: -1}" 509 | if [ "$rep" == "#" ] 510 | then 511 | msg="all ext partition types" 512 | else 513 | msg="root partition only" 514 | fi 515 | printf "%-22s : %s\n" "-L $ext_label" \ 516 | "volume label for $msg." 517 | fi 518 | 519 | if ((leave_sd_usb_boot)) 520 | then 521 | if ((SD_slot_dst)) 522 | then 523 | msg="leave SD card cmdline.txt bootable to USB." 524 | elif ((SD_slot_boot)) 525 | then 526 | msg="install boot to USB cmdline.txt on SD card." 527 | else 528 | msg="-l ignored. Src or dst is not a SD card slot." 529 | fi 530 | printf "%-22s : %s\n" "-l SD to USB boot mode" "$msg" 531 | fi 532 | 533 | if [ "$setup_args" != "" ] 534 | then 535 | printf "%-22s : %s\n" "Run setup script" "$setup_command $setup_args" 536 | else 537 | printf "%-22s : no.\n" "Run setup script" 538 | fi 539 | 540 | if ((have_grub)) 541 | then 542 | printf "%-22s : %s\n" "Run grub" \ 543 | "grub-install --root-directory=$clone /dev/$dst_disk" 544 | fi 545 | printf "%-22s : %s.\n" "Verbose mode" "$verbose" 546 | printf "%-23s:\n" "-----------------------" 547 | } 548 | 549 | ext_label() 550 | { 551 | pnum=$1 552 | fs_type=$2 553 | flag=$3 554 | label_arg="" 555 | 556 | if [ "$ext_label" != "" ] && [[ "$fs_type" == *"ext"* ]] 557 | then 558 | rep="${ext_label: -1}" 559 | if [ "$rep" == "#" ] 560 | then 561 | label_arg=${ext_label:: -1} 562 | label_arg="$flag $label_arg$pnum" 563 | elif ((pnum == root_part_num)) 564 | then 565 | label_arg="$flag $ext_label" 566 | fi 567 | fi 568 | printf -v "${4}" "%s" "$label_arg" 569 | } 570 | 571 | get_src_disk() 572 | { 573 | partition=${1#/dev/} 574 | disk=${partition:: -1} 575 | num="${partition: -1}" 576 | if [[ $disk == *"mmcblk"* ]] 577 | then 578 | SD_slot_boot=1 579 | disk=${disk:0:7} 580 | src_part_base=${disk}p 581 | fi 582 | printf -v "${2}" "%s" "$disk" 583 | printf -v "${3}" "%s" "$num" 584 | } 585 | 586 | 587 | # ==== source (booted) disk info and default mount list 588 | # 589 | src_boot_dev=`findmnt /boot -o source -n | grep "/dev/"` 590 | src_root_dev=`findmnt / -o source -n | grep "/dev/"` 591 | SD_slot_boot=0 592 | SD_slot_dst=0 593 | src_part_base="" 594 | 595 | boot_part_num=0 596 | alt_root_part_num=0 597 | 598 | 599 | if [ "$src_boot_dev" == "" ] 600 | then 601 | get_src_disk "$src_root_dev" "src_disk" "unused" 602 | else 603 | get_src_disk "$src_boot_dev" "src_disk" "boot_part_num" 604 | fi 605 | 606 | get_src_disk "$src_root_dev" "src_root_disk" "root_part_num" 607 | 608 | if [ "$src_disk" == "" ] 609 | then 610 | echo "Cannot find booted device." 611 | exit 1 612 | fi 613 | 614 | if [ "$src_part_base" == "" ] 615 | then 616 | src_part_base=$src_disk 617 | fi 618 | 619 | if [ "$src_disk" != "$src_root_disk" ] 620 | then 621 | if ((SD_slot_boot)) 622 | then 623 | # Handle SD card boots with root on different USB disk device. 624 | # But will assume SD card has a root partition just above its root. 625 | # 626 | alt_root_part_num="$root_part_num" 627 | root_part_num=$((boot_part_num + 1)) 628 | else 629 | echo $" 630 | Boot and root are on different disks and it's not a SD card boot. 631 | Don't know how to partition the destination disk!" 632 | exit 1 633 | fi 634 | fi 635 | 636 | # src_root_dev, if on device other than booted, is not in src_partition_table 637 | # and src_fdisk_table, but is in src_df_table and src_mount_table 638 | # 639 | src_partition_table=$(parted -m "/dev/$src_disk" unit s print | tr -d ';') 640 | src_fdisk_table=$(fdisk -l /dev/$src_disk | grep "^/dev/") 641 | 642 | tmp=$(df | grep -e "^/dev/$src_disk" -e "^/dev/root" -e "$src_root_dev" \ 643 | | tr -s " ") 644 | dev=${src_root_dev#/dev/} 645 | src_df_table=$(echo "$tmp" | sed "s/root/$dev/") 646 | 647 | n_src_parts=$(echo "$src_partition_table" | tail -n 1 | cut -d ":" -f 1) 648 | src_disk_size=$(echo "$src_partition_table" \ 649 | | grep "^/dev/$src_disk" | cut -d ":" -f 2 | tr -d 's') 650 | 651 | line=$(fdisk -l /dev/$src_disk | grep "Disk identifier:") 652 | src_disk_ID=${line#*x} 653 | 654 | src_mount_table=$(findmnt -o source,target -n -l \ 655 | | grep -e "^/dev/$src_disk" -e "^$src_root_dev" | tr -s " ") 656 | n_mounts=$(echo "$src_mount_table" | wc -l) 657 | 658 | if ((alt_root_part_num > 0 && n_src_parts < 2)) 659 | then 660 | echo $" 661 | Booted disk has only one partition and the root is from another device. 662 | Don't know how to partition the destination disk! 663 | " 664 | exit 1 665 | fi 666 | 667 | 668 | line=$(echo "$src_fdisk_table" | grep "Extended") 669 | if [ "$line" != "" ] 670 | then 671 | dev=$(echo "$line" | cut -d " " -f 1) 672 | ext_part_num="${dev: -1}" 673 | else 674 | ext_part_num=0 675 | fi 676 | 677 | 678 | for ((p = 1; p <= n_src_parts; p++)) 679 | do 680 | line=$(echo "$src_partition_table" | grep -e "^${p}:") 681 | if [ "$line" == "" ] 682 | then 683 | src_exists[p]=0 684 | continue 685 | fi 686 | src_exists[p]=1 687 | 688 | if ((p == root_part_num)) 689 | then 690 | src_partition[p]=${src_root_dev#/dev/} 691 | src_device[p]=$src_root_dev 692 | else 693 | src_partition[p]="${src_part_base}${p}" 694 | src_device[p]="/dev/${src_partition[p]}" 695 | fi 696 | 697 | # parted sectors are 512 bytes 698 | src_start_sector[p]=$(echo "$line" | cut -d ":" -f 2 | tr -d 's') 699 | src_size_sectors[p]=$(echo "$line" | cut -d ":" -f 4 | tr -d 's') 700 | 701 | part_type=$(echo "$line" | cut -d ":" -f 5) 702 | 703 | src_mounted_dir[p]=$(echo "$src_mount_table" \ 704 | | grep -m 1 -e "^${src_device[p]}" | cut -d " " -f 2) 705 | if [ "${src_mounted_dir[p]}" != "" ] 706 | then 707 | src_sync_part[p]=1 708 | else 709 | src_sync_part[p]=0 710 | fi 711 | 712 | src_name[p]="" 713 | if [ "$part_type" != "" ] 714 | then 715 | src_fs_type[p]="$part_type" 716 | else 717 | src_fs_type[p]="--" 718 | fi 719 | src_label[p]="--" 720 | 721 | if [ "${src_mounted_dir[p]}" == "/" ] 722 | then 723 | src_name[p]="root" 724 | # 725 | # If root on device other than booted SD card, root_part_num assumed to be 726 | # booted /boot part_num + 1 and alt_root_part_num is from root device. 727 | # 728 | elif ((p == root_part_num)) && ((alt_root_part_num > 0)) 729 | then 730 | src_name[p]="root**" 731 | elif ((p == ext_part_num)) 732 | then 733 | src_fs_type[p]="EXT" 734 | elif [[ "$part_type" == *"linux-swap"* ]] 735 | then 736 | src_fs_type[p]="swap" 737 | elif [ "${src_mounted_dir[p]}" != "" ] 738 | then 739 | src_name[p]="${src_mounted_dir[p]}" 740 | fi 741 | 742 | if [[ "$part_type" == *"ext"* ]] 743 | then 744 | label=`e2label ${src_device[p]} 2> /dev/null` 745 | if [ "$label" != "" ] 746 | then 747 | src_label[p]="$label" 748 | fi 749 | fi 750 | done 751 | 752 | 753 | # command line 754 | # 755 | setup_args="" 756 | edit_fstab_name="" 757 | ext_label="" 758 | verbose="no" 759 | 760 | force_initialize=0 761 | force_2_parts=0 762 | force_sync=0 763 | all_sync=0 764 | usage_error=0 765 | unattended=0 766 | Unattended=0 767 | quiet=0 768 | custom_sync=0 769 | leave_sd_usb_boot=0 770 | convert_to_partuuid=0 771 | p1_size_new=0 772 | 773 | while [ "$1" ] 774 | do 775 | case "$1" in 776 | -v|--verbose) 777 | verbose="yes" 778 | rsync_options=${rsync_options}v 779 | ;; 780 | -u|--unattended) 781 | unattended=1 782 | ;; 783 | -U|--Unattended-init) 784 | unattended=1 785 | Unattended=1 786 | ;; 787 | -q|--quiet) 788 | unattended=1 789 | quiet=1 790 | rsync_options=${rsync_options}q 791 | ;; 792 | --exclude=*|--exclude-from=*) 793 | exclude_useropt="${exclude_useropt} $1" 794 | ;; 795 | -s|--setup) 796 | shift 797 | if ! command -v $setup_command > /dev/null 798 | then 799 | echo "Cannot find script $setup_command for setup arg \"$1\"." 800 | usage_error=1 801 | fi 802 | if [ "$setup_args" == "" ] 803 | then 804 | setup_args="$1" 805 | else 806 | setup_args="$setup_args $1" 807 | fi 808 | ;; 809 | -e|--edit-fstab) 810 | shift 811 | edit_fstab_name=$1 812 | ;; 813 | -f|--force-initialize) 814 | force_initialize=1 815 | ;; 816 | -f2) 817 | force_initialize=1 818 | force_2_parts=1 819 | ;; 820 | -p|--p1-size) 821 | shift 822 | p1_size_arg=$1 823 | p1_size_new=$1 824 | if [[ $p1_size_arg =~ ^[0-9MG]+$ ]] 825 | then 826 | if [[ $p1_size_new == *"M" ]] 827 | then 828 | size=$(echo $p1_size_new | cut -d M -f 1) 829 | p1_size_new=$(($size * 1024 * 1024 / 512)) 830 | elif [[ $p1_size_new == *"G" ]] 831 | then 832 | size=$(echo $p1_size_new | cut -d G -f 1) 833 | p1_size_new=$(($size * 1024 * 1024 * 1024 / 512)) 834 | fi 835 | 836 | if [[ $p1_size_new =~ ^[0-9]+$ ]] 837 | then 838 | if ((!force_sync && p1_size_new < 200 * 1024)) 839 | then 840 | echo "Setting /boot partition size less than 100 MB seems wrong so will not try." 841 | echo " Use -F before -p to override." 842 | exit 1 843 | fi 844 | else 845 | echo "Confused by -p $p1_size_arg." 846 | exit 1 847 | fi 848 | else 849 | echo "Invalid character in -p size. Use digits + M or G like: -p 256M" 850 | exit 1 851 | fi 852 | ;; 853 | -x) 854 | set -x 855 | ;; 856 | -a|--all-sync) 857 | all_sync=1 858 | ;; 859 | -m|--mountdir) 860 | shift 861 | mount_ok=0 862 | for ((p = 1; p <= n_src_parts; p++)) 863 | do 864 | if ((!src_exists[p])) 865 | then 866 | continue 867 | fi 868 | if ((!custom_sync)) && ((p != root_part_num)) 869 | then 870 | src_sync_part[p]=0 871 | fi 872 | if [ "${src_mounted_dir[p]}" == "$1" ] 873 | then 874 | src_sync_part[p]=1 875 | mount_ok=1 876 | fi 877 | done 878 | if ((!mount_ok)) 879 | then 880 | echo "Asking to clone directory \"$1\", but it is not mounted." 881 | usage_error=1 882 | fi 883 | custom_sync=1 884 | ;; 885 | -L|--label_partitions) 886 | shift 887 | ext_label=$1 888 | ;; 889 | -l|--leave-sd-usb-boot) 890 | leave_sd_usb_boot=1 891 | ;; 892 | -F|--Force-sync) 893 | force_sync=1 894 | ;; 895 | --convert-fstab-to-partuuid) 896 | convert_to_partuuid=1 897 | ;; 898 | -V|--version) 899 | echo $PGM Version: $version 900 | exit 0 901 | ;; 902 | -h|--help) 903 | usage 904 | ;; 905 | *) 906 | if [ "$dst_disk" != "" ] 907 | then 908 | echo "Bad arg: $1" 909 | echo "Run $PGM with -h or no args for usage." 910 | exit 1 911 | fi 912 | dst_disk=$1 913 | dir=`expr substr $dst_disk 1 5` 914 | if [ "$dir" == "/dev/" ] 915 | then 916 | dst_disk=${dst_disk#/dev/} 917 | fi 918 | ;; 919 | esac 920 | shift 921 | done 922 | 923 | if ((custom_sync)) && ((all_sync)) 924 | then 925 | echo "-m and -a options at the same time conflict." 926 | exit 1 927 | fi 928 | if ((custom_sync)) && ((force_initialize)) 929 | then 930 | echo "-m and -f options at the same time conflict." 931 | exit 1 932 | fi 933 | if [[ "$verbose" == "yes" ]] && ((quiet)) 934 | then 935 | echo "-q and -v options at the same time conflict." 936 | exit 1 937 | fi 938 | 939 | if ((usage_error)) 940 | then 941 | echo "" 942 | exit 1 943 | fi 944 | 945 | if ((convert_to_partuuid)) 946 | then 947 | unattended=0 948 | Unattended=0 949 | 950 | fstab=/etc/fstab 951 | fstab_tmp=/tmp/fstab 952 | fstab_save=${fstab}.${PGM}-save 953 | confirm "This will change your $fstab, are you sure?" "abort" 954 | 955 | cp $fstab $fstab_tmp 956 | printf "\nConverting $fstab from device names to PARTUUID\n" 957 | count=0 958 | for ((p = 1; p <= n_src_parts; p++)) 959 | do 960 | if grep -q "^/dev/${src_partition[p]}" $fstab_tmp 961 | then 962 | partuuid=$(lsblk -n -o PARTUUID /dev/${src_partition[p]}) 963 | sed -i "s/\/dev\/${src_partition[p]}/PARTUUID=$partuuid/" $fstab_tmp 964 | printf " Editing $fstab, changing /dev/${src_partition[p]} to $partuuid\n" 965 | ((++count)) 966 | fi 967 | done 968 | if ((count)) 969 | then 970 | cp $fstab $fstab_save 971 | cp $fstab_tmp $fstab 972 | printf "Your original fstab is backed up to $fstab_save\n" 973 | 974 | cmdline_txt=/boot/cmdline.txt 975 | cmdline_save=$cmdline_txt.${PGM}-save 976 | if [ -f $cmdline_txt ] && grep -q "$src_root_dev" $cmdline_txt 977 | then 978 | root_part=${src_partition[root_part_num]} 979 | partuuid=$(lsblk -n -o PARTUUID $src_root_dev) 980 | if [ "$partuuid" != "" ] 981 | then 982 | cp $cmdline_txt $cmdline_save 983 | sed -i "s/\/dev\/$root_part/PARTUUID=$partuuid/" $cmdline_txt 984 | printf " Editing $cmdline_txt, changing root=$src_root_dev to root=PARTUUID=$partuuid\n" 985 | printf "Your original cmdline.txt is backed up to $cmdline_save\n" 986 | fi 987 | fi 988 | else 989 | printf "Could not find any $src_disk partition names in $fstab, nothing changed.\n" 990 | fi 991 | rm $fstab_tmp 992 | echo "" 993 | exit 0 994 | fi 995 | 996 | # dst_mount_flag enumerations: 997 | live=1 998 | temp=2 999 | fail=3 1000 | 1001 | for ((p = 1; p <= n_src_parts; p++)) 1002 | do 1003 | if ((!src_exists[p])) 1004 | then 1005 | continue 1006 | fi 1007 | blocks=0 1008 | dst_mount_flag[p]=0 1009 | 1010 | if [ "${src_mounted_dir[p]}" != "" ] 1011 | then # df blocks are 1024 bytes 1012 | dst_mount_flag[p]=$live 1013 | blocks=$(echo "$src_df_table" \ 1014 | | grep -m 1 "^${src_device[p]}" | cut -d " " -f 3) 1015 | # in case intializing, get n_src_parts to compare to dest space 1016 | elif ((p == n_src_parts)) \ 1017 | || ((all_sync)) \ 1018 | && [ "${src_fs_type[p]}" != "EXT" ] \ 1019 | && [ "${src_fs_type[p]}" != "swap" ] \ 1020 | && [ "${src_fs_type[p]}" != "" ] 1021 | then 1022 | if mount ${src_device[p]} $clone 1023 | then 1024 | sleep 1 1025 | blocks=$(df | grep "^${src_device[p]}" \ 1026 | | tr -s " " | cut -d " " -f 3) 1027 | umount $clone 1028 | dst_mount_flag[p]=$temp 1029 | if ((all_sync)) 1030 | then 1031 | src_sync_part[p]=1 1032 | fi 1033 | else 1034 | dst_mount_flag[p]=$fail 1035 | fi 1036 | fi 1037 | src_used_sectors[p]=$((blocks * 2)) 1038 | done 1039 | 1040 | # ==== destination disk checks 1041 | # 1042 | if [ "$dst_disk" = "" ] 1043 | then 1044 | echo "No destination disk given." 1045 | usage 1046 | fi 1047 | 1048 | chk_disk=`cat /proc/partitions | grep -m 1 $dst_disk` 1049 | 1050 | if [ "$chk_disk" == "" ] 1051 | then 1052 | echo $" 1053 | Cannot find '$dst_disk' in the partition table. The partition table is:" 1054 | cat /proc/partitions 1055 | exit 1 1056 | fi 1057 | 1058 | dst_part_base=$dst_disk 1059 | 1060 | if [[ ${chk_disk: -1} =~ ^[0-9]$ ]] 1061 | then 1062 | if [[ $dst_disk == *"mmcblk"* ]] 1063 | then 1064 | SD_slot_dst=1 1065 | dst_part_base=${dst_disk}p 1066 | if [ "$edit_fstab_name" == "" ] \ 1067 | && ! grep -q "^PARTUUID=" /etc/fstab 1068 | then 1069 | edit_fstab_name=$dst_part_base 1070 | assumed_fstab_edit=1 1071 | else 1072 | assumed_fstab_edit=0 1073 | fi 1074 | else 1075 | qecho $" 1076 | Target disk $dst_disk ends with a digit so may be a partition. 1077 | $PGM requires disk names like 'sda' and not partition names like 'sda1'." 1078 | 1079 | confirm "Continue anyway?" "abort" 1080 | fi 1081 | fi 1082 | 1083 | if [ "$src_disk" == "$dst_disk" ] 1084 | then 1085 | echo "Destination disk $dst_disk is the booted disk. Cannot clone!" 1086 | exit 1 1087 | fi 1088 | 1089 | dst_partition_table=$(parted -m "/dev/$dst_disk" unit s print | tr -d ';') 1090 | n_dst_parts=$(echo "$dst_partition_table" | tail -n 1 | cut -d ":" -f 1) 1091 | if [ "$n_dst_parts" == "/dev/$dst_disk" ] 1092 | then 1093 | n_dst_parts=0 1094 | fi 1095 | 1096 | dst_disk_size=$(echo "$dst_partition_table" \ 1097 | | grep "^/dev/$dst_disk" | cut -d ":" -f 2 | tr -d 's') 1098 | dst_root_dev=/dev/${dst_part_base}${root_part_num} 1099 | 1100 | dst_mount_table=$(findmnt -o source,target -n -l \ 1101 | | grep "^/dev/$dst_disk" | tr -s " ") 1102 | 1103 | dst_fdisk_table=$(fdisk -l /dev/$dst_disk | grep "^/dev/") 1104 | line=$(echo "$dst_fdisk_table" | grep "Extended") 1105 | if [ "$line" != "" ] 1106 | then 1107 | dev=$(echo "$line" | cut -d " " -f 1) 1108 | ext_num="${dev: -1}" 1109 | else 1110 | ext_num=0 1111 | fi 1112 | 1113 | for ((p = 1; p <= n_dst_parts; p++)) 1114 | do 1115 | line=$(echo "$dst_partition_table" | grep -e "^${p}:") 1116 | if [ "$line" == "" ] 1117 | then 1118 | dst_exists[p]=0 1119 | continue 1120 | fi 1121 | dst_exists[p]=1 1122 | 1123 | part="${dst_part_base}${p}" 1124 | dst_partition[p]="$part" 1125 | dst_device[p]="/dev/$part" 1126 | 1127 | dst_start_sector[p]=$(echo "$line" | cut -d ":" -f 2 | tr -d 's') 1128 | dst_size_sectors[p]=$(echo "$line" | cut -d ":" -f 4 | tr -d 's') 1129 | 1130 | part_type=$(echo "$line" | cut -d ":" -f 5) 1131 | if [ "$part_type" != "" ] 1132 | then 1133 | dst_fs_type[p]="$part_type" 1134 | else 1135 | dst_fs_type[p]="--" 1136 | fi 1137 | dst_label[p]="--" 1138 | 1139 | if [[ "$part_type" == *"linux-swap"* ]] 1140 | then 1141 | dst_fs_type[p]="swap" 1142 | elif [[ "$part_type" == *"ext"* ]] 1143 | then 1144 | label=`e2label ${dst_device[p]} 2> /dev/null` 1145 | if [ "$label" != "" ] 1146 | then 1147 | dst_label[p]="$label" 1148 | fi 1149 | elif ((p == ext_num)) 1150 | then 1151 | dst_fs_type[p]="EXT" 1152 | fi 1153 | done 1154 | 1155 | fs_match=1 1156 | root_part_match=0 1157 | first_part_mismatch=0 1158 | 1159 | initialize=$((force_initialize)) 1160 | 1161 | for ((p = 1; p <= n_src_parts; p++)) 1162 | do 1163 | if ((!src_exists[p])) 1164 | then 1165 | continue 1166 | fi 1167 | stype=${src_fs_type[p]} 1168 | dtype=${dst_fs_type[p]} 1169 | if [ "$stype" != "$dtype" ] 1170 | then 1171 | tmp_match=0 1172 | if [[ "$stype" == *"fat"* ]] && [[ "$dtype" == *"fat"* ]] 1173 | then 1174 | tmp_match=1 1175 | elif [[ "$stype" == *"ext"* ]] && [[ "$dtype" == *"ext"* ]] 1176 | then 1177 | tmp_match=1 1178 | fi 1179 | if ((tmp_match && p == root_part_num)) 1180 | then 1181 | root_part_match=1 1182 | fi 1183 | if ((!tmp_match)) && ((src_sync_part[p])) 1184 | then 1185 | first_part_mismatch=$p 1186 | fs_match=0 1187 | break 1188 | fi 1189 | fi 1190 | done 1191 | 1192 | 1193 | if ((!fs_match)) 1194 | then 1195 | initialize=1 1196 | fs_match_string="do not match" 1197 | else 1198 | fs_match_string="OK, they match" 1199 | fi 1200 | 1201 | if ((initialize)) && ((quiet)) 1202 | then 1203 | echo "Quiet mode asked for but an initialize is required - can't clone!" 1204 | exit 1 1205 | fi 1206 | 1207 | if ((!initialize && p1_size_new > 0)) 1208 | then 1209 | echo "Cannot specify a -p size for partition 1 unless initializing." 1210 | echo " Use -f or -f2" 1211 | exit 1 1212 | fi 1213 | 1214 | if ((initialize && p1_size_new > 0 && n_image_parts > 2)) 1215 | then 1216 | echo "Cannot specify a -p size for partition 1 unless cloning to only 2 partitions." 1217 | echo " Use -f2" 1218 | exit 1 1219 | fi 1220 | 1221 | if ((!force_sync && p1_size_new > dst_disk_size * 8 / 10)) 1222 | then 1223 | echo "-p1 size > 80% of destination disk size seems wrong so will not try." 1224 | echo " Use -F to override." 1225 | exit 1 1226 | fi 1227 | 1228 | for ((p = n_dst_parts; p >= 1; --p)) 1229 | do 1230 | if ((!dst_exists[p])) 1231 | then 1232 | continue 1233 | fi 1234 | dir=$(echo "$dst_mount_table" \ 1235 | | grep -e "^${dst_device[p]}" | cut -d " " -f 2) 1236 | unmount_or_abort "$dir" \ 1237 | "Destination disk partition ${dst_device[p]} is mounted on $dir." 1238 | done 1239 | 1240 | mounted_dev=$(findmnt $clone -o source -n) 1241 | unmount_or_abort "$mounted_dev" \ 1242 | "Directory $clone is already mounted with $mounted_dev." 1243 | 1244 | mounted_dev=$(findmnt $clone_src -o source -n) 1245 | unmount_or_abort "$mounted_dev" \ 1246 | "Directory $clone_src is already mounted with $mounted_dev." 1247 | 1248 | mounted_dev=$(findmnt /mnt -o source -n) 1249 | unmount_or_abort "$mounted_dev" "$mounted_dev is currently mounted on /mnt." 1250 | 1251 | 1252 | if [ ! -d $clone ] 1253 | then 1254 | mkdir $clone 1255 | fi 1256 | if [ ! -d $clone_src ] 1257 | then 1258 | mkdir $clone_src 1259 | fi 1260 | 1261 | # Do not include a dhpys swapfile in rsync. It regenerates at boot. 1262 | # 1263 | if [ -f /etc/dphys-swapfile ] 1264 | then 1265 | swapfile=`cat /etc/dphys-swapfile | grep ^CONF_SWAPFILE | cut -f 2 -d=` 1266 | if [ "$swapfile" = "" ] 1267 | then 1268 | swapfile=/var/swap 1269 | fi 1270 | exclude_swapfile="--exclude $swapfile" 1271 | fi 1272 | 1273 | if ((grub_auto)) && [ -d /boot/grub ] && command -v grub-install 1274 | then 1275 | have_grub=1 1276 | else 1277 | have_grub=0 1278 | fi 1279 | 1280 | print_partitions 1281 | 1282 | if ((initialize)) 1283 | then 1284 | if ((unattended && !Unattended)) 1285 | then 1286 | echo $" 1287 | Unattended -u option not allowed when initializing. 1288 | Use -U for unattended even if initializing. 1289 | " 1290 | exit 1 1291 | fi 1292 | 1293 | n_image_parts=$((force_2_parts ? 2 : n_src_parts)) 1294 | 1295 | if ((force_initialize)) 1296 | then 1297 | reason="forced by option" 1298 | elif ((n_dst_parts < n_image_parts)) 1299 | then 1300 | reason="partition number mismatch: $n_image_parts -> $n_dst_parts" 1301 | else 1302 | reason="FS types conflict" 1303 | fi 1304 | 1305 | start_sector=${src_start_sector[$n_image_parts]} 1306 | last_part_sectors=$((dst_disk_size - start_sector)) 1307 | last_part_used=${src_used_sectors[$n_image_parts]} 1308 | last_part_space=$(( (last_part_sectors > last_part_used) ? 1 : 0 )) 1309 | 1310 | if ((last_part_sectors < 7812)) 1311 | then 1312 | printf "%-22s : %s\n" "** FATAL **" \ 1313 | "Initialize needed - $reason" 1314 | printf "%-22s : %s %s %s\n" "" \ 1315 | "But destination is too small to clone" "$n_image_parts" "partitions." 1316 | readable_MiB $((start_sector + 7812)) "512" min_size 1317 | printf "%-22s : %s\n" "" \ 1318 | "Minimum destination size required is $min_size." 1319 | if ((n_image_parts > 2)) 1320 | then 1321 | printf "%-22s : %s\n" " " \ 1322 | "Possible options:" 1323 | if ((raspbian)) 1324 | then 1325 | printf "%-22s : %s\n" " " \ 1326 | " Use -f2 to force a two partition initialize clone." 1327 | fi 1328 | printf "%-22s : %s\n" " " \ 1329 | " Use -m to limit partitions to clone." 1330 | printf "%-22s : %s\n" " " \ 1331 | " Manually create custom partitions that can work." 1332 | printf "%-22s : %s\n" " " \ 1333 | " Larger destination disk.." 1334 | fi 1335 | printf "%-23s:\n" "-----------------------" 1336 | exit 1 1337 | fi 1338 | 1339 | readable_MiB $((last_part_sectors + 7812)) "512" image_space_readable 1340 | 1341 | echo "== Initialize: IMAGE partition table - $reason ==" 1342 | print_image_actions 1343 | print_options 1344 | 1345 | printf "%-22s : %s\n" "** WARNING **" \ 1346 | "All destination disk $dst_disk data will be overwritten!" 1347 | 1348 | if ((raspbian_buster && p1_size_new == 0 && src_size_sectors[1] < 400000)) 1349 | then 1350 | printf "%-22s : %s\n" "** WARNING **" \ 1351 | "Your source /boot partition is smaller than the" 1352 | printf "%-22s : %s\n" "" \ 1353 | " Raspbian Buster 256M standard. Consider using" 1354 | printf "%-22s : %s\n" "" \ 1355 | " the '-p 256M' option to avoid /boot clone errors." 1356 | fi 1357 | 1358 | abort=0 1359 | if ((!last_part_space)) 1360 | then 1361 | readable_MiB $last_part_used "512" used_readable 1362 | printf "%-22s : %s\n" "** WARNING **" \ 1363 | "Destination last partition resize to $image_space_readable" 1364 | printf "%-22s : %s\n" "" \ 1365 | " is too small to hold source used $used_readable." 1366 | if [ "$n_image_parts" == "$root_part_num" ] && ((!force_sync)) 1367 | then 1368 | printf "%-22s : %s\n" "** FATAL **" \ 1369 | "This is the root partition, so aborting!" 1370 | printf "%-22s : %s\n" "" \ 1371 | " Use -F to override." 1372 | abort=1 1373 | elif ((!force_sync)) 1374 | then 1375 | printf "%-22s : %s\n" "" \ 1376 | " The partition SYNC is skipped, use -F to override." 1377 | else 1378 | printf "%-22s : %s\n" "" \ 1379 | " ** Syncing anyway as you asked with -F. **" 1380 | fi 1381 | fi 1382 | printf "%-23s:\n" "-----------------------" 1383 | if ((abort)) 1384 | then 1385 | exit 1 1386 | fi 1387 | confirm "Initialize and clone to the destination disk ${dst_disk}?" "abort" 1388 | 1389 | if ((!Unattended)) && [ "$ext_label" == "" ] 1390 | then 1391 | printf "Optional destination ext type file system label (16 chars max): " 1392 | read ext_label 1393 | fi 1394 | 1395 | start_time=`date '+%H:%M:%S'` 1396 | start_sec=$(date '+%s') 1397 | 1398 | image_to_sector=${src_start_sector[1]} 1399 | count=$((image_to_sector / 2 / 1024 + 4)) # in MiB blocks for dd bs=1M 1400 | 1401 | printf "\nInitializing\n" 1402 | printf " Imaging past partition 1 start.\n" 1403 | sync 1404 | printf " => dd if=/dev/$src_disk of=/dev/$dst_disk bs=1M count=$count ..." 1405 | dd if=/dev/$src_disk of=/dev/$dst_disk bs=1M count=$count &> /tmp/$PGM-output 1406 | if [ "$?" != 0 ] 1407 | then 1408 | printf "\n dd failed. See /tmp/$PGM-output.\n" 1409 | printf " Try running $PGM again.\n\n" 1410 | exit 1 1411 | fi 1412 | echo "" 1413 | sync 1414 | sleep 1 1415 | sfd0=$(sfdisk -d /dev/$src_disk) 1416 | if ((force_2_parts && (n_src_parts > n_image_parts))) 1417 | then 1418 | remove_part_start=${src_partition[3]} 1419 | sfd0=$(echo "$sfd0" | sed -e "/\/dev\/$remove_part_start/,\$d") 1420 | fi 1421 | 1422 | part="${src_part_base}$n_image_parts" 1423 | sfd1=$(echo "$sfd0" | sed "\/dev\/$part/s/size=[^,]*,//") 1424 | 1425 | if ((ext_part_num > 0 && !force_2_parts)) 1426 | then 1427 | part="${src_part_base}$ext_part_num" 1428 | sfd1=$(echo "$sfd1" | sed "\/dev\/$part/s/size=[^,]*,//") 1429 | fi 1430 | 1431 | if ((p1_size_new > 0)) 1432 | then 1433 | p1_size_orig=$(echo $sfd1 | grep -Po "size= \K[^ ,]*") 1434 | p2_start_orig=$(echo $sfd1 | grep -Po "\/dev\/${src_part_base}2 : start= \K[^ ,]*") 1435 | p2_start_new=$((p2_start_orig + p1_size_new - p1_size_orig)) 1436 | tmp=$(echo "$sfd1" | sed -e "s/$p2_start_orig/$p2_start_new/") 1437 | sfd1=$(echo "$tmp" | sed -e "s/$p1_size_orig/$p1_size_new/") 1438 | printf " Resizing both destination disk partitions ..." 1439 | else 1440 | printf " Resizing destination disk last partition ..." 1441 | fi 1442 | 1443 | for ((x = 0; x < 3; ++x)) 1444 | do 1445 | sleep $((x + 1)) 1446 | sfdisk --force /dev/$dst_disk &> /tmp/$PGM-output <<< "$sfd1" 1447 | if [ "$?" == 0 ] 1448 | then 1449 | break 1450 | fi 1451 | if ((x == 2)) 1452 | then 1453 | printf "\n====$PGM\n==orig:\n%s\n\n==edited:\n%s\n" \ 1454 | "$sfd0" "$sfd1" >> /tmp/$PGM-output 1455 | printf "\n Resize failed. See /tmp/$PGM-output.\n" 1456 | printf " Try running $PGM again.\n\n" 1457 | 1458 | # Don't let dst disk keep source disk ID. Can lead to remounts. 1459 | new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ") 1460 | qprintf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk > /dev/null 1461 | exit 1 1462 | fi 1463 | done 1464 | printf "\n Resize success.\n" 1465 | printf " Changing destination Disk ID ..." 1466 | sync 1467 | sleep 2 1468 | 1469 | new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ") 1470 | qprintf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk > /dev/null 1471 | sync 1472 | sleep 2 1473 | partprobe "/dev/$dst_disk" 1474 | sleep 2 1475 | echo "" 1476 | 1477 | for ((p = n_image_parts + 1; p <= n_src_parts; p++)) 1478 | do 1479 | src_sync_part[p]=0 1480 | done 1481 | 1482 | for ((p = 1; p <= n_image_parts; p++)) 1483 | do 1484 | if ((!src_exists[p])) 1485 | then 1486 | continue 1487 | fi 1488 | dst_dev=/dev/${dst_part_base}${p} 1489 | fs_type=${src_fs_type[$p]} 1490 | if ((p == ext_part_num)) \ 1491 | || [ "$fs_type" == "--" ] 1492 | then 1493 | continue 1494 | fi 1495 | 1496 | if [ "$fs_type" == "fat16" ] 1497 | then 1498 | mkfs_type="vfat" 1499 | elif [ "$fs_type" == "fat32" ] 1500 | then 1501 | mkfs_type="vfat -F 32" 1502 | else 1503 | mkfs_type=$fs_type 1504 | fi 1505 | 1506 | if [ "${src_mounted_dir[p]}" == "/boot" ] && ((p == 1)) 1507 | then 1508 | ext_label $p "$fs_type" "-L" label 1509 | printf " => mkfs -t $mkfs_type $label $dst_dev ..." 1510 | yes | mkfs -t $mkfs_type $label $dst_dev &>> /tmp/$PGM-output 1511 | echo "" 1512 | else 1513 | if [ "$fs_type" == "swap" ] 1514 | then 1515 | printf " => mkswap $dst_dev\n" 1516 | mkswap $dst_dev &>> /tmp/$PGM-log 1517 | elif ((p != ext_part_num)) 1518 | then 1519 | if [ "${src_mounted_dir[p]}" != "" ] || ((p == n_image_parts)) 1520 | then 1521 | ext_label $p $fs_type "-L" label 1522 | printf " => mkfs -t $mkfs_type $label $dst_dev ..." 1523 | yes | mkfs -t $mkfs_type $label $dst_dev &>> /tmp/$PGM-output 1524 | echo "" 1525 | if ((p == n_image_parts)) 1526 | then 1527 | if ((!last_part_space)) 1528 | then 1529 | src_sync_part[p]=$force_sync 1530 | else 1531 | src_sync_part[p]=1 1532 | fi 1533 | fi 1534 | else 1535 | printf " => dd if=${src_device[$p]} of=$dst_dev bs=1M ..." 1536 | dd if=${src_device[$p]} of=$dst_dev bs=1M &>> /tmp/$PGM-output 1537 | if [ "$?" != 0 ] 1538 | then 1539 | printf "\n dd failed. See /tmp/$PGM-output.\n" 1540 | else 1541 | echo "" 1542 | fi 1543 | ext_label $p $fs_type "" label 1544 | if [ "$label" != "" ] 1545 | then 1546 | echo " e2label $dst_dev $label" 1547 | e2label $dst_dev $label 1548 | fi 1549 | fi 1550 | fi 1551 | fi 1552 | done 1553 | ext_label="" 1554 | else 1555 | qecho "== SYNC $src_disk file systems to $dst_disk ==" 1556 | print_sync_actions 1557 | print_options 1558 | 1559 | informed=0 1560 | space_ok=1 1561 | all_sync_mount_ok=1 1562 | if ((force_sync)) 1563 | then 1564 | err="WARNING" 1565 | else 1566 | err="FATAL" 1567 | fi 1568 | for ((p = 1; p <= n_src_parts; p++)) 1569 | do 1570 | if ((!src_exists[p] || !dst_exists[p])) 1571 | then 1572 | continue 1573 | fi 1574 | if ((${dst_size_sectors[p]} < ${src_used_sectors[p]})) \ 1575 | && ((src_sync_part[p])) 1576 | then 1577 | qprintf "%-22s : %s\n" "** $err **" \ 1578 | "Partition $p: source used > destination space." 1579 | space_ok=$force_sync 1580 | informed=1 1581 | fi 1582 | if ((all_sync && dst_mount_flag[p] == fail)) 1583 | then 1584 | qprintf "%-22s : %s\n" "** $err **" \ 1585 | "Partition $p: mount failed, cannot sync." 1586 | all_sync_mount_ok=$force_sync 1587 | informed=1 1588 | fi 1589 | done 1590 | if ((informed)) 1591 | then 1592 | qprintf "%-23s:" "-----------------------" 1593 | fi 1594 | 1595 | if ((!space_ok || !all_sync_mount_ok)) 1596 | then 1597 | printf "\nAborting!\n" 1598 | if ((!space_ok)) 1599 | then 1600 | printf " Use -F to override used > space fail or -m to select mounts to clone.\n" 1601 | fi 1602 | if ((!all_sync_mount_ok)) 1603 | then 1604 | printf " Use -a -F to sync all except failed mounts.\n" 1605 | fi 1606 | exit 1 1607 | fi 1608 | 1609 | if ((raspbian_buster && dst_size_sectors[1] < 500000)) 1610 | then 1611 | qprintf "%-22s : %s\n" "** WARNING **" \ 1612 | "Your destination /boot partition is smaller than the" 1613 | qprintf "%-22s : %s\n" "" \ 1614 | " Raspbian Buster 256M standard. Consider initializing" 1615 | if ((n_dst_parts <= 2)) 1616 | then 1617 | qprintf "%-22s : %s\n" "" \ 1618 | " with '-f' or '-f2' along with the '-p 256M' options." 1619 | else 1620 | qprintf "%-22s : %s\n" "" \ 1621 | " destination disk (gparted) with new partition sizes." 1622 | fi 1623 | qprintf "%-23s:\n" "-----------------------" 1624 | fi 1625 | 1626 | confirm "Ok to proceed with the clone?" "abort" 1627 | start_time=`date '+%H:%M:%S'` 1628 | start_sec=$(date '+%s') 1629 | fi 1630 | 1631 | line=$(fdisk -l /dev/$dst_disk | grep "Disk identifier:") 1632 | dst_disk_ID=${line#*x} 1633 | if [ "$dst_disk_ID" == "$src_disk_ID" ] 1634 | then 1635 | qecho "Destination disk has same Disk ID as source, changing it." 1636 | new_id=$(od -A n -t x -N 4 /dev/urandom | tr -d " ") 1637 | qprintf "x\ni\n0x$new_id\nr\nw\nq\n" | fdisk /dev/$dst_disk | grep changed 1638 | sync 1639 | sleep 2 1640 | partprobe "/dev/$dst_disk" 1641 | sleep 2 1642 | 1643 | line=$(fdisk -l /dev/$dst_disk | grep "Disk identifier:") 1644 | dst_disk_ID=${line#*x} 1645 | if [ "$dst_disk_ID" == "$src_disk_ID" ] 1646 | then 1647 | qecho " Failed to set a new Disk ID." 1648 | fi 1649 | fi 1650 | 1651 | sync 1652 | qprintf "\nSyncing file systems (can take a long time)\n" 1653 | 1654 | sync_msg_done=0 1655 | for ((p = 1; p <= n_src_parts; p++)) 1656 | do 1657 | if ((!src_exists[p])) 1658 | then 1659 | continue 1660 | fi 1661 | if ((src_sync_part[p] && dst_mount_flag[p] == temp)) 1662 | then 1663 | if ((!sync_msg_done)) 1664 | then 1665 | qprintf "Syncing unmounted partitions:\n" 1666 | fi 1667 | sync_msg_done=1 1668 | dst_dev=/dev/${dst_part_base}${p} 1669 | fs_type=${src_fs_type[$p]} 1670 | ext_label $p $fs_type "" label 1671 | if [ "$label" != "" ] 1672 | then 1673 | qecho " e2label $dst_dev $label" 1674 | e2label $dst_dev $label 1675 | fi 1676 | 1677 | mount_partition ${src_device[p]} $clone_src "" 1678 | mount_partition $dst_dev $clone "$clone_src" 1679 | unmount_list="$clone_src $clone" 1680 | rsync_file_system "${clone_src}/" "${clone}" "" 1681 | unmount_list "$unmount_list" 1682 | fi 1683 | done 1684 | 1685 | qprintf "Syncing mounted partitions:\n" 1686 | 1687 | fs_type=${src_fs_type[$root_part_num]} 1688 | ext_label $root_part_num $fs_type "" label 1689 | if [ "$label" != "" ] 1690 | then 1691 | qecho " e2label $dst_root_dev $label" 1692 | e2label $dst_root_dev $label 1693 | fi 1694 | 1695 | mount_partition $dst_root_dev $clone "" 1696 | unmount_list="$clone" 1697 | 1698 | rsync_file_system "//" "$clone" "with-root-excludes" 1699 | 1700 | for ((p = 1; p <= n_src_parts; p++)) 1701 | do 1702 | if ((!src_exists[p])) 1703 | then 1704 | continue 1705 | fi 1706 | if ((p != root_part_num && src_sync_part[p] && dst_mount_flag[p] == live)) 1707 | then 1708 | dst_dir=$clone${src_mounted_dir[p]} 1709 | if [ ! -d $dst_dir ] 1710 | then 1711 | mkdir -p $dst_dir 1712 | fi 1713 | 1714 | dst_dev=/dev/${dst_part_base}${p} 1715 | fs_type=${src_fs_type[$p]} 1716 | ext_label $p $fs_type "" label 1717 | if [ "$label" != "" ] 1718 | then 1719 | qecho " e2label $dst_dev $label" 1720 | e2label $dst_dev $label 1721 | fi 1722 | 1723 | mount_partition "$dst_dev" "$dst_dir" "$unmount_list" 1724 | rsync_file_system "${src_mounted_dir[p]}/" "${dst_dir}" "" 1725 | unmount_list="$dst_dir $unmount_list" 1726 | fi 1727 | done 1728 | qecho "" 1729 | 1730 | # Fix PARTUUID or device name references in cmdline.txt and fstab 1731 | # 1732 | fstab=${clone}/etc/fstab 1733 | cmdline_txt=${clone}/boot/cmdline.txt 1734 | 1735 | if [ -f $cmdline_txt ] 1736 | then 1737 | if ((leave_sd_usb_boot && SD_slot_dst)) 1738 | then 1739 | qecho "Leaving SD to USB boot alone." 1740 | cp $cmdline_txt ${clone}/boot/cmdline.boot 1741 | cmdline_txt=${clone}/boot/cmdline.boot 1742 | fi 1743 | if grep -q $src_disk_ID $cmdline_txt 1744 | then 1745 | qecho "Editing $cmdline_txt PARTUUID to use $dst_disk_ID" 1746 | sed -i "s/${src_disk_ID}/${dst_disk_ID}/" "$cmdline_txt" 1747 | elif [ "$edit_fstab_name" != "" ] && grep -q ${src_part_base} $cmdline_txt 1748 | then 1749 | qecho "Editing $cmdline_txt references from $src_part_base to $edit_fstab_name" 1750 | sed -i "s/${src_part_base}/$edit_fstab_name/" "$cmdline_txt" 1751 | fi 1752 | if ((leave_sd_usb_boot && SD_slot_boot)) 1753 | then 1754 | qecho "Copying USB cmdline.txt to SD card to set up USB boot." 1755 | cp /boot/cmdline.txt /boot/cmdline.boot 1756 | cp $cmdline_txt /boot/cmdline.txt 1757 | fi 1758 | fi 1759 | 1760 | if grep -q $src_disk_ID $fstab 1761 | then 1762 | qecho "Editing $fstab PARTUUID to use $dst_disk_ID" 1763 | sed -i "s/${src_disk_ID}/${dst_disk_ID}/g" "$fstab" 1764 | elif [ "$edit_fstab_name" != "" ] && grep -q ${src_part_base} $fstab 1765 | then 1766 | qecho "Editing $fstab references from $src_part_base to $edit_fstab_name" 1767 | sed -i "s/${src_part_base}/${edit_fstab_name}/" "$fstab" 1768 | fi 1769 | 1770 | 1771 | rm -f $clone/etc/udev/rules.d/70-persistent-net.rules 1772 | 1773 | dst_root_vol_name=`e2label $dst_root_dev` 1774 | 1775 | if [ "$dst_root_vol_name" = "" ] 1776 | then 1777 | dst_root_vol_name="no label" 1778 | fi 1779 | 1780 | if ((have_grub)) 1781 | then 1782 | qecho "grub-install --root-directory=$clone /dev/$dst_disk" 1783 | if ((quiet)) 1784 | then 1785 | grub-install --root-directory=$clone /dev/$dst_disk &> /dev/null 1786 | else 1787 | grub-install --root-directory=$clone /dev/$dst_disk 1788 | fi 1789 | fi 1790 | 1791 | if [ "$setup_args" != "" ] 1792 | then 1793 | qprintf "\nRunning setup script: $setup_command $setup_args\n" 1794 | $setup_command $setup_args 1795 | fi 1796 | 1797 | date=`date '+%F %H:%M'` 1798 | echo "$date $HOSTNAME $PGM : clone to $dst_disk ($dst_root_vol_name)" \ 1799 | >> $clone_log 1800 | echo "$date $HOSTNAME $PGM : clone to $dst_disk ($dst_root_vol_name)" \ 1801 | >> ${clone}${clone_log} 1802 | 1803 | 1804 | stop_sec=$(date '+%s') 1805 | clone_sec=$((stop_sec - start_sec)) 1806 | 1807 | stop_time=`date '+%H:%M:%S'` 1808 | 1809 | qecho "===============================" 1810 | qecho "Done with clone to /dev/$dst_disk" 1811 | qprintf " Start - %s End - %s Elapsed Time - %d:%02d\n" \ 1812 | "$start_time" "$stop_time" "$((clone_sec / 60))" "$((clone_sec % 60))" 1813 | 1814 | if ((!unattended)) 1815 | then 1816 | echo -n $" 1817 | Cloned partitions are mounted on $clone for inspection or customizing. 1818 | 1819 | Hit Enter when ready to unmount the /dev/$dst_disk partitions ..." 1820 | 1821 | read resp 1822 | fi 1823 | 1824 | unmount_list "$unmount_list" 1825 | qprintf "===============================\n\n" 1826 | 1827 | exit 0 1828 | -------------------------------------------------------------------------------- /rpi-clone-setup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Usage: rpi-clone-setup {-t|--test} hostname 4 | # eg: sudo rpi-clone-setup bozo 5 | # 6 | # This script is automatically run by rpi-clone (when it is given -s options) 7 | # to setup an alternate hostname. A cloned file system mounted on /mnt/clone 8 | # is expected unless testing with the -t option. 9 | # 10 | # Or, this script can be run by hand at the end of a clone when rpi-clone 11 | # pauses with the cloned file systems still mounted on /mnt/clone. 12 | # 13 | # Or, run this script by hand with -t to process files in test directories 14 | # under /tmp/clone-test. Run -t and look at the files to see if the files 15 | # have been edited OK. 16 | # eg: sudo rpi-clone-setup -t bozo 17 | # 18 | # This is a starter script that handles only /etc/hosts and /etc/hostname. 19 | # Make sure the script works correctly for your /etc/hosts file. 20 | # 21 | # If adding a customization for another file: 22 | # Add the file to file_list. 23 | # If needed, add a mkdir -p line to the "if ((testing))" part. 24 | # Add the scripting necessary to customize the file. 25 | # Test new scripting by running: rpi-clone-setup -t newhostname 26 | # 27 | 28 | file_list="etc/hostname etc/hosts" 29 | 30 | clone=/mnt/clone 31 | clone_test=/tmp/clone-test 32 | 33 | PGM=`basename $0` 34 | 35 | if [ `id -u` != 0 ] 36 | then 37 | echo "You must be root to run $PGM" 38 | exit 0 39 | fi 40 | 41 | function usage 42 | { 43 | echo "Usage: $PGM hostname {-t|--test}" 44 | echo " Eg: $PGM rpi1" 45 | echo " Modify files appropriate to set up for a new host." 46 | echo " Files handled are:" 47 | for file in $file_list 48 | do 49 | echo " $file" 50 | done 51 | echo "" 52 | echo "If testing (-t flag) files are copied and processed to $clone_test" 53 | echo "" 54 | exit 0 55 | } 56 | 57 | testing=0 58 | while [ "$1" ] 59 | do 60 | case "$1" in 61 | -t|--test) 62 | testing=1 63 | ;; 64 | *) 65 | if [ "$newhost" != "" ] 66 | then 67 | echo "Bad args" 68 | usage 69 | fi 70 | newhost=$1 71 | ;; 72 | esac 73 | shift 74 | done 75 | 76 | if [ "$newhost" = "" ] 77 | then 78 | echo -e "You must specify a target hostname\n" 79 | usage 80 | fi 81 | 82 | echo -e "\t$newhost\t- target hostname" 83 | 84 | if ((!testing)) && [ ! -d /mnt/clone/etc ] 85 | then 86 | echo "A destination clone file system is not mounted on /mnt/clone" 87 | echo "Aborting!" 88 | exit 0 89 | fi 90 | 91 | if ((testing)) 92 | then 93 | cd /tmp 94 | rm -rf $clone_test 95 | clone=$clone_test 96 | 97 | mkdir -p $clone/etc 98 | 99 | echo "**********************************************" 100 | echo "Testing setup: copying files to $clone" 101 | for file in $file_list 102 | do 103 | echo " cp /$file $clone/$file" 104 | cp /$file $clone/$file 105 | done 106 | echo "This test run will modify those files." 107 | echo "**********************************************" 108 | echo "" 109 | fi 110 | 111 | 112 | ## 113 | # Set /etc/hostname 114 | # 115 | cd $clone/etc 116 | echo $newhost > hostname 117 | # 118 | # Read it back to verify. 119 | # 120 | echo "$clone/etc/hostname - set new hostname: " 121 | LINE=`cat hostname` 122 | echo -e "$LINE\n" 123 | 124 | 125 | ## 126 | # Edit /etc/hosts - edit the sed command if editing fails for your /etc/hosts. 127 | # 128 | cd $clone/etc 129 | sed -i s/"$HOSTNAME"/"$newhost"/ hosts 130 | # 131 | # Read it back to verify. 132 | # 133 | echo "$clone/etc/hosts - set new hostname \"$newhost\" in lines: " 134 | LINE=`grep $newhost hosts` 135 | echo -e "$LINE\n" 136 | 137 | 138 | ## 139 | # Add more customizations if needed. 140 | # 141 | 142 | 143 | exit 0 144 | 145 | --------------------------------------------------------------------------------