-
Notifications
You must be signed in to change notification settings - Fork 0
/
rpi-clone.sh
executable file
·491 lines (413 loc) · 12.7 KB
/
rpi-clone.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
#!/bin/bash
VERSION=1.2
# Version 1.2 2015/02/17
# * add -x option
# * tweak some echo messages
# Version 1.1 2015/02/16
# * Do not include dphys swapfile in the clone.
# * Add a missing -s flag to one of the parted commands.
# * Dump parted stderr to /dev/null since it now complains about destination
# disk partitions ending outside of disk before I get chance to resize
# the partitions.
# * Strip trailing s from PART_START - (fdisk now doesn't seem to accept...)
#
PGM=`basename $0`
RSYNC_OPTIONS="--force -rltWDEgopt"
# List of extra dirs to create under /mnt.
OPTIONAL_MNT_DIRS="clone mnt sda sdb rpi0 rpi1"
# Where to mount the disk filesystems to be rsynced.
CLONE=/mnt/clone
CLONE_LOG=/var/log/$PGM.log
HOSTNAME=`hostname`
SRC_BOOT_PARTITION_TYPE=`parted /dev/mmcblk0 -ms p | grep "^1" | cut -f 5 -d:`
SRC_ROOT_PARTITION_TYPE=`parted /dev/mmcblk0 -ms p | grep "^2" | cut -f 5 -d:`
if [ `id -u` != 0 ]
then
echo -e "$PGM needs to be run as root.\n"
exit 1
fi
if ! rsync --version > /dev/null
then
echo -e "\nOoops! rpi-clone needs the rsync program but cannot find it."
echo "Make sure rsync is installed:"
echo " $ sudo apt-get update"
echo -e " $ sudo apt-get install rsync\n"
exit 0
fi
if test -e /sbin/fsck.vfat
then
HAVE_FSCK_VFAT="yes"
else
echo "[Note] fsck.vfat was not found."
echo "It is recommended to install dosfstools:"
echo " $ sudo apt-get update"
echo " $ sudo apt-get install dosfstools"
fi
usage()
{
echo ""
echo "usage: $PGM sdN {-f|--force-initialize} {-v|--verbose} {-x}"
echo " Example: $PGM sda"
echo " -v - list all files as they are copied."
echo " -f - force initialize the destination partitions"
echo " -x - use set -x for very verbose bash shell script debugging"
echo ""
echo " Clone (rsync) a running Raspberry Pi file system to a destination"
echo " SD card 'sdN' plugged into a Pi USB port (via a USB card reader)."
echo " $PGM can clone the running system to a new SD card or can"
echo " incrementally rsync to existing backup Raspberry Pi SD cards."
echo ""
echo " If the destination SD card has an existing $SRC_BOOT_PARTITION_TYPE partition 1 and a"
echo " $SRC_ROOT_PARTITION_TYPE partition 2, $PGM assumes (unless using the -f option)"
echo " that the SD card is an existing backup with the partitions"
echo " properly sized and set up for a Raspberry Pi. All that is needed"
echo " is to mount the partitions and rsync them to the running system."
echo ""
echo " If these partitions are not found (or -f), then $PGM will ask"
echo " if it is OK to initialize the destination SD card partitions."
echo " This is done by a partial 'dd' from the running booted device"
echo " /dev/mmcblk0 to the destination SD card /dev/sdN followed by a"
echo " fdisk resize and mkfs.ext4 of /dev/sdN partition 2."
echo " This creates a completed $SRC_BOOT_PARTITION_TYPE partition 1 containing all boot"
echo " files and an empty but properly sized partition 2 rootfs."
echo " The SD card partitions are then mounted and rsynced to the"
echo " running system."
echo ""
echo " The SD card destination partitions will be mounted on $CLONE."
echo " A log will be written to $CLONE_LOG."
echo " It's better to avoid running other disk writing programs"
echo " when running $PGM."
echo ""
echo " Version $VERSION"
exit 0
}
VERBOSE=off
while [ "$1" ]
do
case "$1" in
-v|--verbose)
VERBOSE=on
RSYNC_OPTIONS=${RSYNC_OPTIONS}v
;;
-f|--force-initialize)
FORCE_INITIALIZE=true
;;
-x)
set -x
;;
-h|--help)
usage
;;
*)
if [ "$DST_DISK" != "" ]
then
echo "Bad args"
usage
fi
DST_DISK=$1
;;
esac
shift
done
if [ "$DST_DISK" = "" ]
then
usage
exit 0
fi
if ! cat /proc/partitions | grep -q $DST_DISK
then
echo "Destination disk '$DST_DISK' does not exist."
echo "Plug the destination SD card into a card reader connected to a"
echo "USB port. If it does not show up as '$DST_DISK', then do a"
echo -e "'cat /proc/partitions' to see where it might be.\n"
exit 0
fi
unmount_or_abort()
{
echo -n "Do you want to unmount $1? (yes/no): "
read resp
if [ "$resp" = "y" ] || [ "$resp" = "yes" ]
then
if ! umount $1
then
echo "Sorry, $PGM could not unmount $1."
echo -e "Aborting!\n"
exit 0
fi
else
echo -e "Aborting!\n"
exit 0
fi
}
DST_ROOT_PARTITION=/dev/${DST_DISK}2
DST_BOOT_PARTITION=/dev/${DST_DISK}1
# Check that none of the destination partitions are busy (mounted).
#
DST_ROOT_CURMOUNT=`fgrep "$DST_ROOT_PARTITION " /etc/mtab | cut -f 2 -d ' ' `
DST_BOOT_CURMOUNT=`fgrep "$DST_BOOT_PARTITION " /etc/mtab | cut -f 2 -d ' ' `
if [ "$DST_ROOT_CURMOUNT" != "" ] || [ "$DST_BOOT_CURMOUNT" != "" ]
then
echo "A destination partition is busy (mounted). Mount status:"
echo " $DST_ROOT_PARTITION: $DST_ROOT_CURMOUNT"
echo " $DST_BOOT_PARTITION: $DST_BOOT_CURMOUNT"
if [ "$DST_BOOT_CURMOUNT" != "" ]
then
unmount_or_abort $DST_BOOT_CURMOUNT
fi
if [ "$DST_ROOT_CURMOUNT" != "" ]
then
unmount_or_abort $DST_ROOT_CURMOUNT
fi
fi
TEST_MOUNTED=`fgrep " $CLONE " /etc/mtab | cut -f 1 -d ' ' `
if [ "$TEST_MOUNTED" != "" ]
then
echo "This script uses $CLONE for mounting filesystems, but"
echo "$CLONE is already mounted with $TEST_MOUNTED."
unmount_or_abort $CLONE
fi
if [ ! -d $CLONE ]
then
MNT_MOUNT=`fgrep " /mnt " /etc/mtab | cut -f 1 -d ' ' `
if [ "$MNT_MOUNT" = "" ]
then
mkdir $CLONE
else
echo "$MNT_MOUNT is currently mounted on /mnt."
unmount_or_abort /mnt
mkdir $CLONE
fi
fi
# Borrowed from do_expand_rootfs in raspi-config
expand_rootfs()
{
# Get the starting offset of the root partition
# (with Jessie's parted, now need to strip trailing 's' from PART_START)
PART_START=$(parted /dev/mmcblk0 -ms unit s p \
| grep "^2" | cut -f 2 -d: | cut -f 1 -d s)
[ "$PART_START" ] || return 1
# Return value will likely be error for fdisk as it fails to reload the
# partition table because the root fs is mounted
fdisk /dev/$DST_DISK > /dev/null <<EOF
p
d
2
n
p
2
$PART_START
p
w
q
EOF
}
# =========== Disk Setup and Checks ===========
#
# Check that destination partitions are the right type.
#
DST_BOOT_PARTITION_TYPE=`parted /dev/$DST_DISK -ms p 2>/dev/null \
| grep "^1" | cut -f 5 -d:`
DST_ROOT_PARTITION_TYPE=`parted /dev/$DST_DISK -ms p 2>/dev/null \
| grep "^2" | cut -f 5 -d:`
CLONE_MODE="rsync modified files to existing $DST_DISK file systems"
if [ "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" ] || \
[ "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" ] || \
[ "$FORCE_INITIALIZE" = "true" ]
then
CLONE_MODE="rsync all files to $DST_DISK root file system"
echo ""
if [ "$FORCE_INITIALIZE" = "true" ]
then
echo "Forcing a partition initialization of destination disk $DST_DISK"
else
echo "The destination disk $DST_DISK partition table does not"
echo "match the booted disk /dev/mmcblk0 partition table so a"
echo "destination disk initialize is required."
fi
echo "The existing destination disk '$DST_DISK' partitions are:"
parted /dev/$DST_DISK -s unit MB p 2>/dev/null \
| sed "/^Model/d ; /^Sector/d ; /^Disk Flags/d"
# if [ "$DST_BOOT_PARTITION_TYPE" != "$SRC_BOOT_PARTITION_TYPE" ]
# then
# echo -e " ... Cannot find a destination boot file system of type: $SRC_BOOT_PARTITION_TYPE\n"
# fi
# if [ "$DST_ROOT_PARTITION_TYPE" != "$SRC_ROOT_PARTITION_TYPE" ]
# then
# echo -e " ... Cannot find a destination root file system of type: $SRC_ROOT_PARTITION_TYPE\n"
# fi
echo "*** All data on destination disk $DST_DISK will be overwritten! ***"
echo ""
echo -n "Do you want to initialize the destination disk /dev/$DST_DISK? (yes/no): "
read resp
if [ "$resp" = "y" ] || [ "$resp" = "yes" ]
then
# Image onto the destination disk a beginning fragment of the
# running SD card file structure that spans at least more than
# the start of partition 2.
#
# Calculate the start of partition 2 in MB for the dd.
PART2_START=$(parted /dev/mmcblk0 -ms unit MB p | grep "^2" \
| cut -f 2 -d: | sed s/MB// | tr "," "." | cut -f 1 -d.)
# and add some slop
DD_COUNT=`expr $PART2_START + 8`
echo ""
echo "Imaging the partition structure, copying $DD_COUNT megabytes..."
sync
dd if=/dev/mmcblk0 of=/dev/$DST_DISK bs=1M count=$DD_COUNT
# Partition was copied live so fsck to clean up for possible future
# "Volume was not properly unmounted" warnings.
if [ "$HAVE_FSCK_VFAT" = "yes" ]
then
echo "Running fsck on $DST_BOOT_PARTITION..."
fsck -p $DST_BOOT_PARTITION &> /dev/null
fi
# But, though Partion 1 is now imaged, partition 2 is incomplete and
# maybe the wrong size for the destination SD card. So fdisk it to
# make it fill the rest of the disk and mkfs it to clean it out.
#
echo "Sizing partition 2 (root partition) to use all SD card space..."
expand_rootfs
mkfs.ext4 $DST_ROOT_PARTITION > /dev/null
echo ""
echo "/dev/$DST_DISK is initialized and resized. Its partitions are:"
# fdisk -l /dev/$DST_DISK | grep $DST_DISK
parted /dev/$DST_DISK unit MB p \
| sed "/^Model/d ; /^Sector/d ; /^Disk Flags/d"
SRC_ROOT_VOL_NAME=`e2label /dev/mmcblk0p2`
echo ""
echo "Your booted /dev/mmcblk0p2 rootfs existing label: $SRC_ROOT_VOL_NAME"
echo -n "You may enter a label for the destination rootfs $DST_ROOT_PARTITION: "
read resp
if [ "$resp" != "" ]
then
e2label $DST_ROOT_PARTITION $resp
fi
else
echo -e "Aborting\n"
exit 0
fi
fi
# =========== Setup Summary ===========
#
DST_ROOT_VOL_NAME=`e2label $DST_ROOT_PARTITION`
if [ "$DST_ROOT_VOL_NAME" = "" ]
then
DST_ROOT_VOL_NAME="no label"
fi
echo ""
echo "======== Clone Summary ========"
echo "Clone mode : $CLONE_MODE"
echo "Clone destination disk : $DST_DISK"
echo "Clone destination rootfs : $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on ${CLONE}"
echo "Clone destination bootfs : $DST_BOOT_PARTITION on ${CLONE}/boot"
echo "Verbose mode : $VERBOSE"
echo "==============================="
# If this is an SD card initialization, can watch progress of the clone
# in another terminal with: watch df -h
#
echo -n "Final check, is it Ok to proceed with the clone (yes/no)?: "
read resp
if [ "$resp" != "y" ] && [ "$resp" != "yes" ]
then
echo -e "Aborting the disk clone.\n"
exit 0
fi
#
# =========== End of Setup ===========
# Mount destination filesystems.
echo "=> Mounting $DST_ROOT_PARTITION ($DST_ROOT_VOL_NAME) on $CLONE"
if ! mount $DST_ROOT_PARTITION $CLONE
then
echo -e "Mount failure of $DST_ROOT_PARTITION, aborting!\n"
exit 0
fi
if [ ! -d $CLONE/boot ]
then
mkdir $CLONE/boot
fi
echo "=> Mounting $DST_BOOT_PARTITION on $CLONE/boot"
if ! mount $DST_BOOT_PARTITION $CLONE/boot
then
umount $CLONE
echo -e "Mount failure of $DST_BOOT_PARTITION, aborting!\n"
exit 0
fi
echo "==============================="
# Do not include a dhpys swapfile in the clone. dhpys-swapfile will
# regenerate it at boot.
#
if [ -f /etc/dphys-swapfile ]
then
SWAPFILE=`cat /etc/dphys-swapfile | grep ^CONF_SWAPFILE | cut -f 2 -d=`
if [ "$SWAPFILE" = "" ]
then
SWAPFILE=/var/swap
fi
EXCLUDE_SWAPFILE="--exclude $SWAPFILE"
fi
START_TIME=`date '+%H:%M:%S'`
# Exclude fuse mountpoint .gvfs, various other mount points, and tmpfs
# file systems from the rsync.
#
sync
echo "Starting the filesystem rsync to $DST_DISK"
echo -n "(This may take several minutes)..."
rsync $RSYNC_OPTIONS --delete \
$EXCLUDE_SWAPFILE \
--exclude '.gvfs' \
--exclude '/dev' \
--exclude '/media' \
--exclude '/mnt' \
--exclude '/proc' \
--exclude '/run' \
--exclude '/sys' \
--exclude '/tmp' \
--exclude 'lost\+found' \
// \
$CLONE
# Fixup some stuff
#
for i in dev media mnt proc run sys
do
if [ ! -d $CLONE/$i ]
then
mkdir $CLONE/$i
fi
done
if [ ! -d $CLONE/tmp ]
then
mkdir $CLONE/tmp
chmod a+w $CLONE/tmp
fi
# Some extra optional dirs I create under /mnt
for i in $OPTIONAL_MNT_DIRS
do
if [ ! -d $CLONE/mnt/$i ]
then
mkdir $CLONE/mnt/$i
fi
done
rm -f $CLONE/etc/udev/rules.d/70-persistent-net.rules
DATE=`date '+%F %H:%M'`
echo "$DATE $HOSTNAME $PGM : clone to $DST_DISK ($DST_ROOT_VOL_NAME)" \
>> $CLONE_LOG
echo "$DATE $HOSTNAME $PGM : clone to $DST_DISK ($DST_ROOT_VOL_NAME)" \
>> $CLONE/$CLONE_LOG
STOP_TIME=`date '+%H:%M:%S'`
echo ""
echo "*** Done with clone to /dev/$DST_DISK ***"
echo " Started: $START_TIME Finished: $STOP_TIME"
echo ""
# Pause before unmounting in case I want to inspect the clone results
# or need to custom modify any files on the destination SD clone.
# Eg. modify $CLONE/etc/hostname, $CLONE/etc/network/interfaces, etc
# if I'm cloning into a card to be installed on another Pi.
#
echo -n "Hit Enter when ready to unmount the /dev/$DST_DISK partitions..."
read resp
echo "unmounting $CLONE/boot"
umount $CLONE/boot
echo "unmounting $CLONE"
umount $CLONE
echo "==============================="
exit 0