├── README.md ├── osmc-backup └── osmc-restore /README.md: -------------------------------------------------------------------------------- 1 | # osmc-backup 2 | Backup scripts for copying osmc installations to and from system partitions 3 | 4 | The backup function within OSMC backs up only your libraries and Kodi user settings. A number of people have asked what is the best way to do a complete backup of OSMC. Here is a script which makes a backup of both partitions of a standard RPi SD card or USB disk install. The backup device is assumed to be a local disc or a network share mounted in the /media/ directory and formatted as ext4. If backing up via nfs extended attributes are not copied but osmc does not need them. Each backup is dated by the name of the directory it is in – backups are assumed to be not more frequent than daily – and is a complete copy of the files on the two partitions, except those generated by the system which are not needed for a full restore. Hard links are used to minimise duplication of files. It can also make a separate copy of just the files that have changed or been added since the last backup. 5 | 6 | As written, the script throws away backups after a certain time or so as to keep only the last n backups. You can ‘freeze’ a previous backup by renaming its directory from say 2017-09-30 to 2017-09-30beforeSeptupgrade. There is code to check how much disc space is being used and it could be modified so as to delete old backups when they are are taking up too much space on the backup media. 7 | 8 | Some checks are included, for the existence of the backup media for example, but I can’t claim it’s bomb-proof. There are some progress messages which can be re-directed to a log file for unattended operation. The script does a search for the devices mounted at / and /boot so should work with a USB stick install. 9 | 10 | As at version 0.1.9 of the backup script and 0.1.5 of the restore script, vero4k is supported. Users report the script does work with Vero 2. There is also now a facility to shut down kodi, tvheadend and any other services (user specified) that may write to the filesystem while the backup is happening. 11 | 12 | Everyone will have their own take on how and when to make backups. This script can keep daily copies of your SD card for ever which would be overkill but you can set up your own schedule. I keep backups for a week. The only dependencies are rsync and pv for backup and parted for restore which are all in the repository, and for unattended operation cron, which is in the App Store. A suitable line in /etc/crontab looks like: 13 | 14 | 15 2 * * * root /home/osmc/osmc-backup >> /home/osmc/backlog 15 | 16 | # osmc-restore 17 | The restore script is interactive and is invoked with: 18 | 19 | osmc-restore [-f] path/to/backup/dir [destination] 20 | 21 | You can specify the destination (eg sda) on the commandline or you will be prompted for it. The switch -f forces a format of the destination sd card. If there aren’t suitable partitions on the card, you will be prompted to format it. You can also run osmc-restore / to clone your working system to SD card or USB drive, which does not have to be the same size. 22 | 23 | The restore for vero uses a similar partitioning to RPi. The script makes a tarball of the backup directory on the ext4 partition so it can be installed using a recent copy of a dtb.img and kernel.img from an installation image. These are looked for in the top level of the backup directory and in the script directory. If not found, they have to be copied to the SD card or USB drive manually. 24 | 25 | Comments and suggestions for improvement are welcome. 26 | -------------------------------------------------------------------------------- /osmc-backup: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # A script to make backups of a running linux system. The filesystem mounted at / 4 | # is backed up to a directory called system. User-specified branches of the filesystem 5 | # with other mountpoints, eg /home or /boot are backed up to directories of the same name 6 | 7 | # Copyright (c) Graham Horner, 2017-2019. 8 | # Feel free to use and/or modify for any non-commercial purposes. 9 | # No warranty as to fitness of purpose or suitability 10 | # is given or should be inferred. 11 | 12 | # Any comments or bug reports to discourse.osmc.tv. 13 | 14 | VERSION='0.1.14' ## fix error message when unsupported file format 15 | 16 | ## ********************************************************************* 17 | # Set up the variables for your system 18 | ## ********************************************************************* 19 | 20 | # where do you want the backups? 21 | # the backup device must be mounted at /media/[mountpoint] 22 | # or /media/[user]/[mountpoint] or /mnt/[mountpoint] 23 | # the backup directory can be, say, /media/[mountpoint] or 24 | # any subdirectory of [mountpoint] 25 | 26 | DEFAULTBACKUPDIR="/media/1Text4/osmcbackup" 27 | 28 | # we search mounts for filesystem types which understand 29 | # linux owners/permissions - add yours if not listed 30 | # !! smb/cifs does not support hard links so cannot be used 31 | # !! even if the backup destination is ext formatted 32 | 33 | FSTYPES=(ext xfs reiserfs nfs autofs) 34 | 35 | # backups older than DAYSTOKEEP or older than the oldest of 36 | # BACKUPSTOKEEP will be deleted before making this backup 37 | 38 | # for how many days are you going to keep your backups? 39 | 40 | DAYSTOKEEP=365 41 | 42 | # or how many backups do you want to keep? 43 | 44 | BACKUPSTOKEEP=7 45 | 46 | # the two default partitions on a RPi install (system is /) 47 | # vero has only one (system) partition 48 | # currently, this script does not work with normal directories, only mount points 49 | 50 | PARTSTOBACKUP=(system boot) 51 | 52 | # to minimize the risk of corrupted files or inconsistent file structure 53 | # shut down the following services 54 | 55 | SERVICESTOPAUSE=(mediacenter tvheadend) 56 | 57 | ## ********************************************************************* 58 | # End of normal user input parameters 59 | # See below at ### for more esoteric options 60 | ## ********************************************************************* 61 | 62 | # check if this is being run as root 63 | if (( $(/usr/bin/id -u) != 0 )); then 64 | echo Must be run as root! 65 | exit 1 66 | fi 67 | 68 | # check we have rsync 69 | if ! rsync --version &> /dev/null; then 70 | GETDEPENDS=1 71 | echo -e "\nThis script needs rsync but I can't find it\nTry: sudo apt-get install rsync (or similar)\n" 72 | fi 73 | 74 | # check we have pv 75 | if ! pv --version &> /dev/null; then 76 | GETDEPENDS=1 77 | echo -e "\nThis script needs pv but I can't find it\nTry: sudo apt-get install pv (or similar)\n" 78 | fi 79 | 80 | if (($GETDEPENDS)); then exit 0; fi 81 | # provide for BACKUPDIR to be specified on a commandline 82 | if [ -z "$@" ]; then 83 | BACKUPDIR="$DEFAULTBACKUPDIR" 84 | else 85 | BACKUPDIR="$1" 86 | fi 87 | 88 | # remove a trailing slash 89 | BACKUPDIR=${BACKUPDIR%/} 90 | 91 | # detailed rsync logs are written to the script directory 92 | # and copied into the backup directory at the end of the process 93 | SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" 94 | LOGFILE="$SCRIPTDIR"/rsynclog$(date +%F) 95 | 96 | # using just the date to name the backup directories 97 | ### add time of day for more frequent backups 98 | NOW=$(date +%F) 99 | 100 | echo $(date) Backup version $VERSION 101 | echo Going to back up to $BACKUPDIR/$NOW "(unless I can't find it)." 102 | 103 | ### if backing up to a network resource, you will need to mount it 104 | # eg for an nfs server: 105 | 106 | # cd /media 107 | # mkdir backupshare 108 | # mount.nfs servername/sharename backupshare 109 | 110 | ### if the backup medium is not formatted for linux permissions (eg ntfs) 111 | # you could make an ext4 filesystem image and mount it with: 112 | 113 | # cd /media 114 | # mkdir backup 115 | # mount -o loop /path/to/filesystem/image backup 116 | 117 | # this technique can make for some horribly slow backups and will not work with FAT!! 118 | 119 | MEDIAFOUND=0 120 | FSTYPE="" 121 | if [[ "$BACKUPDIR/" =~ ((^/media/[^/]*)/[^/]*) ]] \ 122 | || [[ "$BACKUPDIR/" =~ ((^/mnt/[^/]*)/[^/]*) ]] ; then 123 | # check if it is mounted in /media/, /mnt/ or /media/user/ 124 | echo looking for mounted media 125 | for i in 1 2; do 126 | DRIVE=${BASH_REMATCH[$i]} 127 | if ! BACKUPMNT=$(findmnt -l | grep ^"$DRIVE "); then 128 | continue 129 | else 130 | for t in ${FSTYPES[@]}; do 131 | if ! [[ $BACKUPMNT =~ [[:space:]]$t.?[[:space:]] ]]; then 132 | continue 133 | else 134 | FSTYPE=$t 135 | echo media found at $DRIVE, accessed as $FSTYPE 136 | MEDIAFOUND=1 137 | if [[ $FSTYPE == "nfs" ]]; then ## ACLs and xattrs are not supported 138 | echo nfs does not support ACLs or extended attributes - these will not be transferred 139 | echo This should not affect OSMC 140 | fi 141 | ### remount with no delayed allocation 142 | #if ! [[ ${BACKUPMNT[3]} =~ nodelalloc ]]; then 143 | # mount -o remount,nodelalloc,${BACKUPMNT[3]} $DRIVE || exit 1 144 | #fi 145 | break 146 | fi 147 | done 148 | if ! (($MEDIAFOUND)); then 149 | echo -e "Found backup media at $DRIVE but it's not in a suitable format\nShould be one of ${FSTYPES[@]}" 150 | exit 1 151 | fi 152 | fi 153 | if (($MEDIAFOUND)); then break; fi 154 | done 155 | fi 156 | 157 | if ! (($MEDIAFOUND)); then 158 | echo media for $BACKUPDIR not mounted in /media or /mnt 159 | exit 1 160 | else 161 | SPACEONDRIVE=$(df --sync --output=avail "$DRIVE" | grep -o "[[:digit:]]*") 162 | printf "%'u%s\n" $SPACEONDRIVE " KiB available on drive" 163 | SPACEFREED=0 164 | fi 165 | 166 | # find the most recent previous backup, count previous backups and delete old ones 167 | LAST= 168 | BACKUPCOUNT=0 169 | OLDESTKEEP=$(date -d "-$(($DAYSTOKEEP))days" +%F) 170 | echo -e "\nCounting and deleting backups older than $OLDESTKEEP or if more than $BACKUPSTOKEEP of them" 171 | if [ -d "$BACKUPDIR" ]; then 172 | for i in $(ls -t "$BACKUPDIR"); do 173 | # ignore directories and files without a date 174 | if ! [[ $i =~ ^[0-9]*-[0-9]*-[0-9]* ]]; then 175 | continue 176 | elif [[ "$BACKUPDIR/$i" != "$BACKUPDIR/$NOW" ]]; then 177 | if [ $BACKUPCOUNT == 0 ]; then 178 | # this should be the most recent backup, irrespective of its name 179 | echo Latest backup from $i kept 180 | LAST="$i" 181 | BACKUPCOUNT=$(( $BACKUPCOUNT + 1 )) 182 | elif [[ $i =~ ^[0-9]*-[0-9]*-[0-9]*$ ]]; then 183 | # any directory with a bare date is counted 184 | if [[ $i < $OLDESTKEEP ]] || (($BACKUPCOUNT >= $BACKUPSTOKEEP - 1)); then 185 | echo Deleting backup from $i 186 | rm -r "$BACKUPDIR/$i" 187 | echo Backup from $i removed 188 | continue 189 | fi 190 | echo Backup from $i kept 191 | BACKUPCOUNT=$(( $BACKUPCOUNT + 1 )) 192 | else 193 | echo Backup called $i kept and not counted 194 | fi 195 | fi 196 | done 197 | SPACEONDRIVE=$(df --sync --output=avail "$DRIVE" | grep -o "[[:digit:]]*") 198 | printf "%s%'u%s\n" "Now " $SPACEONDRIVE " KiB available on drive" 199 | fi 200 | 201 | ### here you may wish to add a check and abort or delete some previous 202 | # backups if there is a danger of filling up the backup device 203 | 204 | function ToggleServices(){ 205 | if ! [ -z $SERVICESTOPAUSE ]; then 206 | for s in ${SERVICESTOPAUSE[@]}; do 207 | echo Going to $1 $s 208 | systemctl $1 $s.service 209 | done 210 | fi 211 | } 212 | 213 | function DieNicely(){ 214 | # umount /media/$1 &> /dev/null 215 | # rmdir /media/$1 216 | ToggleServices "start" 217 | echo $2 218 | echo rsync exited with status \("$?"\) see www.samba.org/ftp/rsync/rsync.html for details. 219 | exit 1 220 | } 221 | 222 | ToggleServices "stop" 223 | 224 | for source in ${PARTSTOBACKUP[@]}; do 225 | # vero does not have two partitions 226 | if [ $(uname -n) == 'vero' ] && [ $source != 'system' ]; then continue; fi 227 | # remove leading and trailing slashes 228 | source=${source%/} 229 | source=${source#/} 230 | # tidy up if partition is still mounted in /media 231 | # umount /media/$source &> /dev/null 232 | RSYNCOPTS="-axHAXii" 233 | if [[ $FSTYPE == "nfs" ]]; then ## ACLs and xattrs are not supported 234 | RSYNCOPTS="-axHii" 235 | fi 236 | EXCLUDES='--exclude=.gvfs' 237 | if [ $source == "system" ]; then 238 | # directories generated by the system 239 | EXCLUDES+=' --exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/run/* --exclude=/tmp/*' 240 | # you probably don't want this directory - the equivalent of Windows *.chk files 241 | EXCLUDES+=' --exclude=/lost+found' 242 | # these mount directories should be excluded automatically by the mount process below 243 | # but just to be sure 244 | EXCLUDES+=' --exclude=/mnt/* --exclude=/media/*' 245 | ### you may like to uncomment the next line - these directories should be unnecessary for a restore 246 | #EXCLUDES+=' --exclude=/var/cache/* --exclude=/var/run/* --exclude=/var/tmp/*' 247 | 248 | # the system partition is mounted somewhere - find the device it is on 249 | SRCDEV=$(findmnt -l | grep "^/\s.*/dev" | sed 's/^\S*\s*\(\S*\).*/\1/') 250 | SRCROOT="/" 251 | else 252 | SRCDEV=$(findmnt -l | grep "^/$source\s.*/dev" | sed 's/^\S*\s*\(\S*\).*/\1/') 253 | SRCROOT="/$source/" 254 | fi 255 | 256 | ### uncomment the following to avoid backing up the rubbish bin (when there is one) 257 | #EXCLUDES+=' --exclude=Trash*/ --exclude=.Trash*/' 258 | 259 | echo -e "\n$source is on $SRCDEV - copying from $SRCROOT" 260 | 261 | if ! [ -d "$BACKUPDIR/$NOW"/$source ]; then 262 | mkdir -p "$BACKUPDIR/$NOW"/$source 263 | elif ! [ $(ls "$BACKUPDIR/$NOW"/$source | wc -l) == 0 ]; then 264 | echo $source has already been backed up today and will be overwritten 265 | if read -n1 -rsp "Press any key within 10 secs to skip .... " -t 10 key; then 266 | echo "" 267 | continue 268 | fi 269 | echo "" 270 | # rsync may not delete links to gash files in the backup directory 271 | echo OK, deleting earlier backup 272 | SPACEONDRIVE0=$(df --sync --output=avail "$DRIVE" | grep -o "[[:digit:]]*") 273 | rm -r "$BACKUPDIR/$NOW/$source"* && echo $BACKUPDIR/$NOW/$source deleted 274 | SPACEONDRIVE1=$(df --sync --output=avail "$DRIVE" | grep -o "[[:digit:]]*") 275 | SPACEFREED=$(($SPACEFREED + $SPACEONDRIVE1 - $SPACEONDRIVE0)) 276 | printf "%s%'u%s\n" "Now " $SPACEONDRIVE1 " KiB available on drive" 277 | fi 278 | 279 | START=$(date +%s) 280 | echo Backing up $source partition to $BACKUPDIR/$NOW/$source 281 | # cd /media 282 | # if ! [[ $SRCDEV == "" ]]; then 283 | # # source is already mounted, but we'll mount it again in /media 284 | # # to avoid recursing into nested mountpoints 285 | # if ! [ -d $source ]; then mkdir $source; fi 286 | # if [ $(ls $source | wc -l) == 0 ]; then mount $SRCDEV $source || exit 1; fi 287 | # # else we assume it's already mounted, (eg if last backup didn't complete) 288 | # # but lets just check 289 | # if ! findmnt -l | grep "^/media/$source " > /dev/null; then 290 | # echo /media/$source is not a mount point 291 | # exit 1 292 | # fi 293 | # else 294 | # echo Source is not a device - not currently supported 295 | # continue 296 | # fi 297 | FIRSTRUN=0 298 | if [ -z "$LAST" ]; then 299 | FIRSTRUN=1 300 | echo No previous backups in "$BACKUPDIR" - making first copy 301 | elif ! [[ $(rsync "$BACKUPDIR/$LAST/$source/" 2> /dev/null | wc -l) > 1 ]]; then 302 | # no previous backup here 303 | FIRSTRUN=1 304 | echo Can\'t find previous backup at "$BACKUPDIR/$LAST/$source/" - making first copy 305 | fi 306 | 307 | echo Counting files ... 308 | FILECOUNT=$(find $SRCROOT -mount | wc -l) 309 | echo $FILECOUNT files and directories to process 310 | 311 | if (($FIRSTRUN)); then 312 | # no previous backup here 313 | echo Backup directory is empty - making first copy 314 | echo rsync "$RSYNCOPTS" $EXCLUDES $SRCROOT "$BACKUPDIR/$NOW/$source" &>> "$LOGFILE" 315 | rsync "$RSYNCOPTS" "$EXCLUDES" "$SRCROOT" "$BACKUPDIR/$NOW/$source" --log-file="$LOGFILE" \ 316 | | pv -lepbts $FILECOUNT >/dev/null \ 317 | || DieNicely $source "rsync stopped while making first backup" 318 | else 319 | echo There are $BACKUPCOUNT previous backups 320 | echo Found latest backup at $BACKUPDIR/$LAST/$source 321 | # make a conventional incremental backup to today's directory 322 | echo Incremental backup: copying new and changed files to "$BACKUPDIR/$NOW/$source" 323 | # if more than one backup in one day, results will be appended 324 | if [ -e "$LOGFILE" ]; then echo >> "$LOGFILE"; fi 325 | ### the rsync -c option does a checksum comparison on each file - slow but sure 326 | echo rsync "$RSYNCOPTS"c "$EXCLUDES" --compare-dest="$BACKUPDIR/$LAST/$source" "$SRCROOT" "$BACKUPDIR/$NOW/$source" >> "$LOGFILE" 327 | rsync "$RSYNCOPTS"c "$EXCLUDES" --compare-dest="$BACKUPDIR/$LAST/$source" "$SRCROOT" "$BACKUPDIR/$NOW/$source" --log-file="$LOGFILE" \ 328 | | pv -lepbts $FILECOUNT >/dev/null \ 329 | || DieNicely $source "rsync stopped while making incremental backup" 330 | ### keep a copy of the new and changed files (optional - comment out if not needed) 331 | echo Making a copy of the new and changed files in "$BACKUPDIR/$NOW/$source-delta" 332 | rsync "$RSYNCOPTS"m --link-dest="$BACKUPDIR/$NOW/$source" "$BACKUPDIR/$NOW/$source/" "$BACKUPDIR/$NOW/$source-delta" --log-file="$LOGFILE" \ 333 | || DieNicely $source "rsync failed while recording changes" 334 | # link all the files from the last backup 335 | echo Cloning unchanged files from previous backup 336 | cp -al --no-clobber "$BACKUPDIR/$LAST/$source" "$BACKUPDIR/$NOW" || DieNicely $source "failed while linking previous backup" 337 | # and remove files which were removed from the source to end up with an exact copy of the source 338 | # --existing prevents new files being added (since the first rsync above) 339 | # and --ignore-existing stops changes to the backup which might propogate to previous backups 340 | echo Deleting files no longer on source 341 | echo >> "$LOGFILE" 342 | echo rsync "$RSYNCOPTS" "$EXCLUDES" --existing --ignore-existing --delete --delete-excluded "$SRCROOT" "$BACKUPDIR/$NOW/$source" >> "$LOGFILE" 343 | rsync "$RSYNCOPTS" "$EXCLUDES" --existing --ignore-existing --delete --delete-excluded "$SRCROOT" "$BACKUPDIR/$NOW/$source" --log-file="$LOGFILE" \ 344 | | pv -lepbts $FILECOUNT >/dev/null \ 345 | || DieNicely $source "rsync stopped while deleting files" 346 | fi 347 | 348 | # cd /media 349 | # umount /media/$source 350 | # rmdir /media/$source 351 | ELAPSED=$(($(date +%s) - $START)) 352 | echo Time to back up $source: $(date -d@${ELAPSED} -u +%Hh:%Mm:%Ss) 353 | done 354 | 355 | mv "$LOGFILE" "$BACKUPDIR/$NOW/" 356 | 357 | # How much space did we use? 358 | #AVAIL=($(df --sync --output=avail "$DRIVE")) 359 | SPACEONDRIVENOW=$(df --sync --output=avail "$DRIVE" | grep -o "[[:digit:]]*") 360 | SPACEUSED=$(($SPACEONDRIVE + $SPACEFREED - $SPACEONDRIVENOW)) 361 | printf "\n%'u%s\n" $SPACEUSED " KiB used in this backup" 362 | printf "%'u%s\n" $SPACEONDRIVENOW " KiB available on drive" 363 | 364 | ToggleServices "start" 365 | 366 | echo -e "All done!\n" 367 | -------------------------------------------------------------------------------- /osmc-restore: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | # A script to restore backups to removable media. It will also format a USB stick or 4 | # SD card suitable for OSMC and restore the system and boot partitions appropriately 5 | 6 | # Copyright (c) Graham Horner, 2017-19. 7 | # Feel free to use and/or modify for any non-commercial purposes. 8 | # No warranty as to fitness of purpose or suitability 9 | # is given or should be inferred. 10 | 11 | # Any comments or bug reports to discourse.osmc.tv. 12 | 13 | VERSION='0.1.12' ## further check for vero and print version 14 | #set -x 15 | 16 | echo $(date) Restore version $VERSION 17 | 18 | # check if this is being run as root 19 | if (( $(/usr/bin/id -u) != 0 )); then 20 | echo Must be run as root! 21 | exit 1 22 | fi 23 | 24 | # check we have parted 25 | if ! parted --version &> /dev/null; then 26 | GETDEPENDS=1 27 | echo -e "\nThis script needs parted but I can't find it\nTry: sudo apt-get install parted (or similar)\n" 28 | fi 29 | 30 | # check we have rsync 31 | if ! rsync --version &> /dev/null; then 32 | GETDEPENDS=1 33 | echo -e "\nThis script needs rsync but I can't find it\nTry: sudo apt-get install rsync (or similar)\n" 34 | fi 35 | 36 | # check we have pv 37 | if ! pv --version &> /dev/null; then 38 | GETDEPENDS=1 39 | echo -e "\nThis script needs pv but I can't find it\nTry: sudo apt-get install pv (or similar)\n" 40 | fi 41 | 42 | if (($GETDEPENDS)); then exit 0; fi 43 | 44 | # Parse arguments 45 | BACKUPDIR="$1" 46 | MEDIA="$2" 47 | FORMAT=0 48 | if [ "$1" == "-f" ]; then 49 | FORMAT=1 50 | BACKUPDIR="$2" 51 | MEDIA="$3" 52 | elif [ "$2" == "-f" ]; then 53 | FORMAT=1 54 | MEDIA="$3" 55 | elif [ "$3" == "-f" ]; then 56 | FORMAT=1 57 | fi 58 | 59 | if [ -z $1 ]; then 60 | echo Usage: "$( basename "${BASH_SOURCE[0]}") [-f] source_device|backupdir|/[system|boot] [restore_device][partition]" 61 | echo Example: "$( basename "${BASH_SOURCE[0]}") /media/backup/2017-04-01 sdc" 62 | echo Example: "$( basename "${BASH_SOURCE[0]}") sdb sdc" 63 | exit 1 64 | fi 65 | 66 | #echo $BACKUPDIR 67 | CLONE=0 68 | LIVE=0 69 | PARTStoRESTORE=(boot system) 70 | 71 | # check if we are only copying one partition 72 | for source in ${PARTStoRESTORE[@]}; do 73 | if [[ "$BACKUPDIR" =~ "$source"$ ]]; then 74 | echo Restoring only $BACKUPDIR 75 | PARTStoRESTORE=($source) 76 | BACKUPDIR=${BACKUPDIR%/$source} 77 | #echo now $BACKUPDIR 78 | if [ -z "$BACKUPDIR" ]; then 79 | CLONE=1 80 | LIVE=1 81 | BACKUPDIR=/ 82 | fi 83 | fi 84 | done 85 | 86 | # see if $BACKUPDIR is a device 87 | if [[ $(lsblk -lo NAME,TYPE | grep "$BACKUPDIR.*disk"$ ) ]] && ! [[ "$BACKUPDIR" =~ ^\. ]] ; then 88 | CLONE=1 89 | BACKUPDEV=($(lsblk -lo NAME,TYPE | grep "$BACKUPDIR.*disk"$ )) 90 | echo Going to copy ${BACKUPDEV[0]} 91 | elif ! [[ "$BACKUPDIR" =~ ^/ ]]; then 92 | # make BACKUPDIR path absolute 93 | BACKUPDIR="$PWD/$BACKUPDIR" 94 | # tidy it 95 | BACKUPDIR=$( pushd "$BACKUPDIR" > /dev/null && pwd && popd > /dev/null ) 96 | else 97 | if [ "$BACKUPDIR" == "/" ]; then 98 | CLONE=1 99 | LIVE=1 100 | echo Going to copy the working filesystem 101 | else 102 | # remove a trailing slash 103 | BACKUPDIR=${BACKUPDIR%/} 104 | fi 105 | fi 106 | 107 | VERO=0 108 | if [[ ${BACKUPDIR^^} =~ "VERO" ]]; then 109 | VERO=1 110 | elif ((LIVE)) && [[ $(cat /proc/cmdline) =~ vero ]]; then 111 | VERO=1 112 | elif ! [ -d "$BACKUPDIR/boot" ] && ! (($CLONE)); then 113 | VERO=1 114 | elif [ $(ls "$BACKUPDIR/system/boot" | wc -l) \> 0 ]; then 115 | VERO=1 116 | fi 117 | 118 | SCRIPTDIR="$( pushd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd && popd > /dev/null )" 119 | 120 | BACKUPFOUND=0 121 | #echo $BACKUPDIR 122 | 123 | if [ -d "$BACKUPDIR" ] || (($CLONE)); then 124 | for source in ${PARTStoRESTORE[@]}; do 125 | 126 | if [ $source == "boot" ] && (($VERO)); then 127 | BACKUPFOUND=$(($BACKUPFOUND + 1)) 128 | continue 129 | fi 130 | 131 | if (($CLONE)); then 132 | if (($LIVE)); then 133 | if [ $source == "system" ]; then 134 | # the system partition is mounted somewhere - find the device it is on 135 | SRCDEV=$(findmnt -l | grep "^/\s.*/dev" | sed 's/^\S*\s*\(\S*\).*/\1/') 136 | else 137 | SRCDEV=$(findmnt -l | grep "^/$source\s.*/dev" | sed 's/^\S*\s*\(\S*\).*/\1/') 138 | fi 139 | else 140 | # partition is defined by device ID so re-mount it 141 | if [ $source == "system" ]; then 142 | SRCDEV="/dev/$BACKUPDIR"2 143 | elif [ $source == "boot" ]; then 144 | SRCDEV="/dev/$BACKUPDIR"1 145 | fi 146 | umount $SRCDEV || echo Cannot unmount $SRCDEV 147 | mkdir -p /media/sdcard/$source 148 | if [ $(ls /media/sdcard/$source | wc -l) == 0 ]; then mount $SRCDEV /media/sdcard/$source || exit 1; fi 149 | # else we assume it's already mounted, (eg if last backup didn't complete) 150 | # but lets just check (SHOULDN'T NEED THIS, NOW) 151 | if ! findmnt -l | grep "^/media/sdcard/$source " &> /dev/null; then 152 | echo /media/sdcard/$source is not a mount point 153 | exit 1 154 | fi 155 | fi 156 | BACKUPFOUND=1 157 | elif [ -d "$BACKUPDIR"/$source ]; then 158 | BACKUPFOUND=$(($BACKUPFOUND + 1)) 159 | else 160 | echo There is no $source directory in $BACKUPDIR, please check 161 | fi 162 | done 163 | if ! (($BACKUPFOUND)); then exit 0; fi 164 | if ((CLONE)) && !((LIVE)); then BACKUPDIR=/media/sdcard; fi 165 | else 166 | echo Cannot find $BACKUPDIR, please check 167 | exit 0 168 | fi 169 | 170 | # have a look for the media to install to 171 | MEDIA=${MEDIA##/dev/} 172 | PARTPREFIX= 173 | while ! lsblk | grep "^$MEDIA " > /dev/null; do 174 | #lsblk -l | grep "\s1\s*\S.*\sdisk" | sed 's/^\(\S*\)\s.*/\1/' 175 | lsblk -lo NAME,SIZE,TYPE | grep "disk"$ 176 | read -rp "Please enter the device name of the media to restore to ... " MEDIA 177 | echo 178 | MEDIA=${MEDIA##/dev/} 179 | PARTPREFIX= 180 | if [[ $MEDIA =~ mmcblk.* ]]; then 181 | PARTPREFIX="p" 182 | fi 183 | if findmnt -l "/dev/$MEDIA$PARTPREFIX"1 > /dev/null && ! findmnt -l "/dev/$MEDIA$PARTPREFIX"1 | grep "/media" > /dev/null; then 184 | read -n1 -rsp "/dev/$MEDIA is mounted but not removable - are you sure (Y/N)? " ANSWER 185 | if ! [[ $ANSWER =~ [Yy] ]]; then 186 | echo 187 | echo Aborting .... 188 | exit 0 189 | else 190 | echo 191 | fi 192 | fi 193 | done 194 | 195 | if [[ $MEDIA =~ mmcblk.* ]]; then 196 | PARTPREFIX="p" 197 | fi 198 | 199 | # trim partition number if both partitions to be restored 200 | PARTNO=0 201 | if [[ $MEDIA =~ (mmcblk[0-9])p([0-9])$ ]]; then 202 | DEVICE=${BASH_REMATCH[1]} 203 | PARTNO=${BASH_REMATCH[2]} 204 | elif ! [[ $MEDIA =~ mmcblk.* ]] && [[ $MEDIA =~ (.*)([0-9])$ ]]; then 205 | DEVICE=${BASH_REMATCH[1]} 206 | PARTNO=${BASH_REMATCH[2]} 207 | else 208 | DEVICE=$MEDIA 209 | fi 210 | if [ ${#PARTStoRESTORE[@]} \> 1 ] || (($FORMAT)); then 211 | MEDIA=$DEVICE 212 | PARTNO=0 213 | fi 214 | 215 | function format(){ 216 | read -n1 -rsp "Going to erase all of /dev/$MEDIA, press Y to continue ... " ANSWER 217 | if ! [[ $ANSWER =~ [Yy] ]]; then 218 | echo 219 | echo Aborting .... 220 | exit 0 221 | else 222 | echo 223 | fi 224 | 225 | if findmnt -l "/dev/$MEDIA$PARTPREFIX"1 > /dev/null; then 226 | echo "/dev/$MEDIA$PARTPREFIX"1 is mounted 227 | PART=1 228 | while findmnt -l "/dev/$MEDIA$PARTPREFIX$PART" > /dev/null; do 229 | if ! umount "/dev/$MEDIA$PARTPREFIX$PART"; then 230 | echo 231 | echo Problem unmounting /dev/$MEDIA$PARTPREFIX$PART 232 | exit 1 233 | else 234 | echo Unmounted "/dev/$MEDIA$PARTPREFIX$PART" 235 | fi 236 | PART=$(($PART + 1)) 237 | done 238 | fi 239 | 240 | # find out how big it is to determine AU size 241 | DISKSIZE=$(lsblk -blo NAME,SIZE | grep "$MEDIA " | sed 's/^\S*\s*\(\S*\)\s.*/\1/') 242 | BLANK=4MiB 243 | PART1=248MiB 244 | if (( DISKSIZE > 33000000000)); then 245 | BLANK=16MiB 246 | PART1=256MiB 247 | fi 248 | 249 | if [[ $MEDIA =~ "mmcblk" ]]; then 250 | echo Formatting SD card $MEDIA 251 | else 252 | echo Formatting USB stick $MEDIA 253 | fi 254 | parted -s /dev/$MEDIA mklabel msdos mkpart primary fat32 $BLANK $PART1 \ 255 | && parted -s /dev/$MEDIA -- mkpart primary ext4 $PART1 -1s 256 | # refresh the partitions in the kernel, but that also mounts them 257 | # if there was already a filesystem there 258 | partprobe /dev/"$MEDIA" 259 | # wait for the system to register the new partitions 260 | while ! [ -e /dev/"$MEDIA$PARTPREFIX"1 ]; do 261 | echo Waiting for /dev/"$MEDIA" 262 | sleep 1 263 | done 264 | parted /dev/"$MEDIA" set 1 lba on 265 | umount /dev/"$MEDIA$PARTPREFIX"1 2> /dev/null 266 | mkfs.vfat -F 32 -s 4 -I /dev/"$MEDIA$PARTPREFIX"1 267 | partprobe /dev/"$MEDIA" 268 | # wait for the system to register the new partitions 269 | while ! [ -e /dev/"$MEDIA$PARTPREFIX"1 ]; do 270 | echo Waiting for /dev/"$MEDIA" 271 | sleep 1 272 | done 273 | umount /dev/"$MEDIA$PARTPREFIX"2 2> /dev/null 274 | mkfs.ext4 /dev/"$MEDIA$PARTPREFIX"2 275 | # run this again to refresh things 276 | partprobe /dev/"$MEDIA" &> /dev/null 277 | while ! [ -e /dev/"$MEDIA$PARTPREFIX"1 ]; do 278 | echo Waiting for /dev/"$MEDIA" 279 | sleep 1 280 | done 281 | } 282 | 283 | if (($FORMAT)); then format; fi 284 | 285 | START=$(date +%s) 286 | 287 | function restore(){ 288 | for source in ${PARTStoRESTORE[@]}; do 289 | PARTNAME= 290 | if [[ $source =~ "boot" ]]; then 291 | PARTTYPE=vfat; PARTSIZEMIN=244000000 292 | CPPARAM="--no-preserve=ownership" 293 | elif [ $source == "system" ]; then 294 | PARTTYPE=ext 295 | if [ "$BACKUPDIR" == '/' ]; then 296 | if [ $1 == "dry" ]; then 297 | echo Checking size of working system 298 | fi 299 | PARTSIZEMIN=($(du -xsB1 "$BACKUPDIR")) 300 | else 301 | if [ $1 == "dry" ]; then 302 | echo Checking size of "$BACKUPDIR/$source" 303 | fi 304 | PARTSIZEMIN=($(du -xsB1 "$BACKUPDIR/$source")) 305 | fi 306 | CPPARAM="-x" # don't copy mounts boot, media, mnt 307 | if [ $1 == "dry" ]; then 308 | printf "%'u%s\n\n" $((${PARTSIZEMIN[0]} / 1024)) "KiB in $source" 309 | fi 310 | fi 311 | for partindex in $(seq 1 3 "$((${#PARTS[@]} - 2))"); do 312 | # do it only once if partition number has been specified 313 | if [ $PARTNO != "0" ]; then 314 | if [ $partindex \> "$(( ($PARTNO - 1)*3 + 1 ))" ]; then break 315 | elif [ $partindex \< "$(( ($PARTNO - 1)*3 + 1 ))" ]; then continue 316 | fi 317 | fi 318 | #echo partition: ${PARTS[$partindex]} 319 | if [[ ${PARTS[$partindex]} =~ "$PARTTYPE" ]]; then 320 | PARTNAME=${PARTS[$(($partindex - 1))]}; #echo $PARTNAME 321 | PARTSIZE=${PARTS[$(($partindex + 1))]}; #echo $PARTSIZE 322 | if (( $PARTSIZE < $PARTSIZEMIN )); then 323 | printf "%s%'u%s\n" "$PARTTYPE partition at $PARTNAME may be too small (" $(($PARTSIZE / 1024)) " KiB)" 324 | read -n1 -rsp "Press Y to continue, any other key to skip: " ANSWER 325 | if ! [[ $ANSWER =~ [Yy] ]]; then 326 | echo 327 | echo Skipping $PARTNAME partition .... 328 | PARTNAME=skip 329 | continue 330 | else 331 | break 332 | fi 333 | elif [ $1 != "dry" ]; then 334 | printf "%s%'u%s\n" "Found $PARTTYPE partition at $PARTNAME with enough space (" $(($PARTSIZE / 1024)) " KiB)" 335 | read -n1 -rsp "Press Y to write $source to this partition, any other key to skip: " ANSWER 336 | if ! [[ $ANSWER =~ [Yy] ]]; then 337 | echo 338 | echo Skipping $PARTNAME partition .... 339 | echo 340 | PARTNAME=skip 341 | continue 342 | else 343 | break 344 | fi 345 | fi 346 | fi 347 | done 348 | if [ -z $PARTNAME ]; then 349 | echo "Cannot find a suitable $PARTTYPE partition for $source" 350 | echo 351 | continue 352 | fi 353 | if [ $PARTNAME == "skip" ]; then continue; fi 354 | PARTSFOUND=$(( $PARTSFOUND + 1 )) 355 | if [ $1 == "dry" ]; then continue; fi 356 | 357 | # check if target is already mounted - it will be if we've just formatted it 358 | TARGET=$(findmnt -lo SOURCE,TARGET "/dev/$PARTNAME" | grep "[(media)|(mnt)]") # | sed 's/^.*\/[\(media\)|\(mnt\)]\/\(.*\)/\/\1\/\3/') 359 | TARGET=${TARGET#/dev/"$PARTNAME "} 360 | echo 361 | #echo target: $TARGET 362 | if ! [ -z $TARGET ] && ! $(echo "$TARGET" | grep "/media" > /dev/null); then 363 | read -n1 -rsp "/dev/$PARTNAME is mounted but not removable - are you sure (Y/N)? " ANSWER 364 | if ! [[ $ANSWER =~ [Yy] ]]; then 365 | echo 366 | echo Aborting .... 367 | exit 0 368 | else 369 | echo 370 | fi 371 | fi 372 | # tried to use an existing mount but safer to do it this way 373 | if ! [ -z "$TARGET" ]; then 374 | if ! umount "$TARGET";then 375 | echo Cannot unmount $TARGET - is it busy? 376 | exit 1 377 | fi 378 | fi 379 | mkdir -p /media/$source 380 | #ls -al /media/$source 381 | mount /dev/"$PARTNAME" /media/$source 382 | #echo $(findmnt -l "/dev/$PARTNAME") 383 | TARGET=/media/$source 384 | 385 | if [ $source == 'system' ] && [ "$BACKUPDIR" == '/' ]; then 386 | echo Copying root directory to /dev/$PARTNAME 387 | EXCLUDES='--exclude=.gvfs' 388 | # directories generated by the system 389 | EXCLUDES+=' --exclude=/dev/* --exclude=/proc/* --exclude=/sys/* --exclude=/run/* --exclude=/tmp/*' 390 | # you probably don't want this directory - the equivalent of Windows *.chk files 391 | EXCLUDES+=' --exclude=/lost+found' 392 | # these mount directories should be excluded automatically by the -x switch in rsync 393 | # but just to be sure 394 | EXCLUDES+=' --exclude=/mnt/* --exclude=/media/*' 395 | ### you may like to uncomment the next line - these directories should be unnecessary for a restore 396 | #EXCLUDES+=' --exclude=/var/cache/* --exclude=/var/run/* --exclude=/var/tmp/*' 397 | #cp -a "$CPPARAM" "$BACKUPDIR". "$TARGET" 398 | rsync -axcH "$EXCLUDES" --delete --delete-excluded --info=progress2 --no-specials --no-devices "$BACKUPDIR" "$TARGET" 399 | elif [ $source == 'boot' ] && (($VERO)); then 400 | echo Copying Vero installer files to /dev/$PARTNAME 401 | FILESFOUND=1 402 | for img in kernel dtb; do 403 | if [ -f "$BACKUPDIR/../$img.img" ]; then 404 | echo Copying $img.img to /dev/$PARTNAME 405 | cp "$BACKUPDIR/../$img.img" "$TARGET" 406 | elif [ -f "$SCRIPTDIR/$img.img" ]; then 407 | echo Copying $img.img to /dev/$PARTNAME 408 | cp "$SCRIPTDIR/$img.img" "$TARGET" 409 | else 410 | FILESFOUND=0 411 | fi 412 | done 413 | if ! ((FILESFOUND)) ; then 414 | echo "Can't find kernel.img and/or dtb.img in ${BACKUPDIR%/*}." 415 | echo For Vero, you need copies of kernel.img and \(for Vero4k\) dtb.img from a recent install image. 416 | echo Just copy these two files onto the FAT partition of your SD card or USB stick 417 | fi 418 | rm "$TARGET"/filesystem.sh &> /dev/null 419 | echo Writing filesystem.sh to $TARGET 420 | cat <<- EOF > "$TARGET"/filesystem.sh 421 | mkdir /mnt/backup 422 | DEVICE=\$( mount | grep "/mnt/boot" | awk '{ print \$1 }' ) 423 | DEVICE=\${DEVICE%1}2 424 | mount \$DEVICE /mnt/backup 425 | if [ -f /mnt/backup/filesystem.tar ]; then 426 | SYSTEMSIZE=\$(du -s /mnt/backup/filesystem.tar | awk '{ print \$1 }') 427 | cat /mnt/backup/filesystem.tar | pv -s \${SYSTEMSIZE}k -n | tar -xf - -C /mnt/root 428 | elif [ -f /mnt/backup/filesystem.tar.xz ]; then 429 | SYSTEMSIZE=\$(du -s /mnt/backup/filesystem.tar.xz | awk '{ print \$1 }') 430 | cat /mnt/backup/filesystem.tar.xz | pv -s \${SYSTEMSIZE}k -n | tar -xJf - -C /mnt/root 431 | # provide for restore direct from backup via ln -s filesystem 432 | elif [ -h /mnt/backup/filesystem ]; then 433 | SYSTEMSIZE=\$(du -s /mnt/backup/filesystem | awk '{ print \$1 }') 434 | find /mnt/backup/filesystem -type s -exec rm {} \\; 435 | tar -cf - -C /mnt/backup/filesystem ./ | pv -s \${SYSTEMSIZE}k -n | tar -xf - -C /mnt/root 436 | else 437 | SYSTEMSIZE=\$(du -s /mnt/backup | awk '{ print \$1 }') 438 | find /mnt/backup -type s -exec rm {} \\; 439 | tar -cf - -C /mnt/backup ./ | pv -s \${SYSTEMSIZE}k -n | tar -xf - -C /mnt/root 440 | fi 441 | sync 442 | umount /mnt/backup 443 | EOF 444 | 445 | else 446 | if [ -f "$TARGET"/filesystem.tar ]; then 447 | rm "$TARGET"/filesystem.tar 448 | fi 449 | echo Copying $BACKUPDIR/$source to /dev/$PARTNAME 450 | if (($VERO)); then 451 | tar -cf - -C "$BACKUPDIR/$source" ./ | pv -s ${PARTSIZEMIN[0]} | cat - > "$TARGET"/filesystem.tar 452 | else 453 | tar -cf - -C "$BACKUPDIR/$source" ./ | pv -s ${PARTSIZEMIN[0]} | tar -xf - -C "$TARGET" 454 | fi 455 | fi 456 | echo unmounting "$TARGET" 457 | echo 458 | umount "$TARGET" 459 | if [ -d "$TARGET" ]; then rmdir "$TARGET"; fi 460 | umount /media/sdcard/$source &> /dev/null && \ 461 | rmdir /media/sdcard/$source &> /dev/null 462 | RESTORED=$(( $RESTORED + 1 )) 463 | done 464 | } 465 | 466 | RESTORED=0 467 | PARTSFOUND=0 468 | while [ $PARTSFOUND \< ${#PARTStoRESTORE[@]} ]; do 469 | # check what partitions we have 470 | PARTS=($(lsblk -lo NAME | grep "$MEDIA$PARTPREFIX\S$")) 471 | echo Looking for existing partitions on $DEVICE 472 | echo Partitions found: ${#PARTS[@]} ${PARTS[@]} 473 | echo 474 | if (( ${#PARTS[@]} >= ${#PARTStoRESTORE[@]} )); then 475 | PARTS=($(lsblk -blo NAME,FSTYPE,SIZE | grep "$MEDIA$PARTPREFIX\S\s")) 476 | restore dry 477 | else 478 | echo Not enough partitions on $DEVICE 479 | fi 480 | if [ $PARTSFOUND \< ${#PARTStoRESTORE[@]} ]; then 481 | echo Cannot not find one or more suitable partitions 482 | echo "Partitions on $DEVICE are as follows" 483 | parted /dev/$DEVICE print 484 | read -n1 -rsp "Do you want to re-format the card and try again (Y/N)? " ANSWER 485 | echo 486 | if [[ $ANSWER =~ [Yy] ]]; then 487 | format 488 | START=$(date +%s) 489 | PARTSFOUND=0 490 | else 491 | break 492 | fi 493 | fi 494 | done 495 | restore all 496 | rmdir /media/sdcard &> /dev/null 497 | if [ $RESTORED \< ${#PARTStoRESTORE[@]} ]; then 498 | echo WARNING: only $RESTORED of ${#PARTStoRESTORE[@]} partitions were restored 499 | fi 500 | 501 | ELAPSED=$(($(date +%s) - $START)) 502 | echo Time to restore: $(date -d@${ELAPSED} -u +%Hh:%Mm:%Ss) 503 | 504 | 505 | --------------------------------------------------------------------------------