Created
September 22, 2017 17:58
-
-
Save pmeulen/2bb9099244f4fa9585f052c9df8a13cc to your computer and use it in GitHub Desktop.
Script to repair a Time Machine network volume
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
set -e | |
############################################################################### | |
# This script tries to repair a Time Machine *network* backup (i.e. an APF | |
# share containing a sparsebundle) that is shared over a network using e.g. an | |
# Apple TimeCapsule, a NAS, Raspberry PI, ... | |
# The script must be run on the computer that created the backup | |
# | |
# This procedure is based on various pieces of wisdom floating around the | |
# internet. A thank you to everybody who shared! | |
# | |
# !! Use at your own risk. | |
# !! No guarantee whatsoever that this does what you want it to do | |
# | |
# !! Whenever possible use a wired network connection. | |
# !! This will much improve the speed and reliability of the repair process | |
# | |
# !! You need to set the configuration below before running this script | |
############################################################################### | |
############################################################################### | |
# When you get "Time Machine completed a verification of your backups. To | |
# improve reliability, Time Machine must create a new backup for you" that is | |
# of course what you could do. However, if you let Time Machine create a new | |
# backup it will delete *all* your existing backups and start a new backup. That | |
# takes a long time, and you will lose all your previous backups. | |
# | |
# Alternatively you can use this script to (try to) repair your existing backup. | |
# This script: | |
# - clears the "immutable" flag that Time Machine set when it declared the volume | |
# unfit | |
# - mounts the TimeMachine backup image (i.e. the .sparebundle) and | |
# uses fsck_hfs the verify and repair its filesystem | |
# - Upadates com.apple.TimeMachine.MachineID.plist to mark the backup as "healthy" | |
############################################################################### | |
############################################################################### | |
# Start configuration | |
# Name of the computer that created the backup. | |
# This is the "Computer Name" in the "Sharing" System Preferences pane. | |
# TODO: Set computer name | |
COMPUTER_NAME="" | |
# Set AFP_HOST, AFP_USER and AFP_VOLUME below to specify the location of the disk containing | |
# the sparsebundle. This is the location that contains the the disk that contains the | |
# $COMPUTER_NAME.sparsebundle file | |
# Hostname (DNS name, IP address or Bonjour) of the host to connect to | |
# TODO: Set host name | |
AFP_HOST="" | |
# Username for authenticating to AFP | |
# TODO: Set user name | |
AFP_USER="TimeMachine" | |
# Name of the AFP share to mount | |
# TODO: Set AFP share name | |
AFP_VOLUME="TMBackup" | |
# End configuration | |
############################################################################### | |
if [[ $(whoami) != 'root' ]]; then | |
echo "Script must be run as root" | |
exit 1 | |
fi | |
# Prevent a timemache backup from interfering | |
echo "Disabling Time Machine" | |
/usr/bin/tmutil disable | |
# Create directory for mountpoint | |
MOUNTDIR=`mktemp -q -d /Volumes/Timemachine.repair.XXXX` | |
if [[ $? != 0 ]]; then | |
echo "Could not create directory for mountpoint" | |
exit 1 | |
fi | |
echo "Created temporary mountpoint \"$MOUNTDIR\"" | |
# Mount share. -i asks for password when needed | |
echo "Mounting "afp://$AFP_USER@$AFP_HOST/$AFP_VOLUME"..." | |
mount_afp -i afp://$AFP_USER@$AFP_HOST/$AFP_VOLUME $MOUNTDIR | |
if [[ $? != 0 ]]; then | |
echo "Mount failed" | |
echo "Removing temporary mountpoint" | |
rmdir $MOUNTDIR | |
exit 1 | |
fi | |
echo "Mounted AFP volume at \"$MOUNTDIR\"" | |
SPARSEBUNDLE=$MOUNTDIR/$COMPUTER_NAME.sparsebundle | |
if [ ! -d $SPARSEBUNDLE ]; then | |
echo "Could not find sparsebundle at \"$SPARSEBUNDLE\"" | |
/sbin/umount $MOUNTDIR | |
/bin/rmdir $MOUNTDIR | |
exit 1 | |
fi | |
echo "Found sparsebundle at $SPARSEBUNDLE" | |
PLIST=${SPARSEBUNDLE}/com.apple.TimeMachine.MachineID.plist | |
echo "Reading com.apple.TimeMachine.MachineID.plist from the sparsebundle" | |
if [ ! -e ${SPARSEBUNDLE} ]; then | |
echo "Could not find com.apple.TimeMachine.MachineID.plist in the sparsebudle" | |
/sbin/umount $MOUNTDIR | |
/bin/rmdir $MOUNTDIR | |
exit 1 | |
fi | |
echo "Reading the verification state of the volume..." | |
VERIFICATION_STATE=`/usr/bin/defaults read $PLIST VerificationState` | |
echo VerificationState=$VERIFICATION_STATE | |
echo "0=OK; 2=failed" | |
# The -v -v option will list any changes made | |
echo "Clearing user immutable flag in sparsebundle" | |
/usr/bin/chflags -R -v -v nouchg "$SPARSEBUNDLE" | |
if [[ $? != 0 ]]; then | |
echo "chflags failed" | |
exit 1 | |
fi | |
# Attach the sparsebundle, but disable any checks | |
echo "Attaching sparsebundle ..." | |
DEV_SPARSEBUNDLE=`/usr/bin/hdiutil attach -nomount -readwrite -noverify -noautofsck "$SPARSEBUNDLE" | /usr/bin/grep Apple_HFS | /usr/bin/cut -f 1 -d " "` | |
if [[ `echo $DEV_SPARSEBUNDLE | cut -c 1-5` != '/dev/' ]]; then | |
echo "Attaching sparsebundle failed" | |
exit 1 | |
fi | |
echo "Sparsebundle was attached at \"$DEV_SPARSEBUNDLE\"" | |
# Do a fsck of the sparsebundle | |
# When things are really broken, add a "-r" to the fsck to rebuild the B-tree | |
echo "fsck'ing sparsebundle. This will take some time..." | |
# -d for debug; -c to specify max memory used by the fsck | |
/sbin/fsck_hfs -f -y -d -c 8192m $DEV_SPARSEBUNDLE | |
# This is the magic sauce the will let TimeMachine accept the disk again | |
# The "com.apple.TimeMachine.MachineID.plist" file contains a VerificationState property | |
# that needs to be 0 for TM to accept the disk. | |
# If nonzero it will contain a RecoveryBackupDeclinedDate as well that needs to be removed | |
echo "Creating .backup and then updating \"$PLIST\"" | |
/bin/cp $PLIST $PLIST.backup | |
# PM: defaults also converts plist from xml to binary format. Should work, but not what I want. | |
# So use ugly sed construction below instead (thanks, google) | |
#/usr/bin/defaults write $PLIST VerificationState 0 | |
#/usr/bin/defaults delete $PLIST RecoveryBackupDeclinedDate | |
/usr/bin/sed -e '/RecoveryBackupDeclinedDate/{N;d;}' \ | |
-e '/VerificationState/{n;s/2/0/;}' \ | |
"$PLIST.backup" \ | |
> "$PLIST" | |
echo "Detaching sparsebundle" | |
/usr/bin/hdiutil detach $DEV_SPARSEBUNDLE | |
echo "Unmounting AFP volume" | |
/sbin/umount $MOUNTDIR | |
if [ -d $SPARSEBUNDLE ]; then | |
echo "Removing temporary mountpoint" | |
/bin/rmdir $MOUNTDIR | |
fi | |
echo "Enabling timemachine" | |
/usr/bin/tmutil enable |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
found this a year too late... lol
Thanks for building this!