-
Notifications
You must be signed in to change notification settings - Fork 1
/
tmbless.sh
executable file
·168 lines (135 loc) · 6.17 KB
/
tmbless.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
#!/usr/bin/env bash
# Copyright (c) 2023 Joshua Lee Ockert <[email protected]>
#
# THIS WORK IS PROVIDED "AS IS" WITH NO WARRANTY OF ANY KIND. THE IMPLIED
# WARRANTIES OF MERCHANTABILITY, FITNESS, NON-INFRINGEMENT, AND TITLE ARE
# EXPRESSLY DISCLAIMED. NO AUTHOR SHALL BE LIABLE UNDER ANY THEORY OF LAW
# FOR ANY DAMAGES OF ANY KIND RESULTING FROM THE USE OF THIS WORK.
#
# Permission to use, copy, modify, and/or distribute this work for any
# purpose is hereby granted, provided this notice appears in all copies.
#
# SPDX-License-Identifier: ISC
function dispusage() {
if [ ${#1} -gt 0 ]; then
printf "%s\n\n" "${1}"
fi
echo "\
TIME MACHINE BLESS
USAGE
${0} <snapshot directory>
DESCRIPTION
Time Machine Bless marks a snapshot directory as valid and recognizable
by Time Machine.
It does this by modifying the metadata of the snapshot directory so that
the it accurately reflects the date the snapshot was created and the
metadata of the 'drive' subdirectory within that snapshot directory
matches the current drive.
These modifications should allow restoration of files within the Time
Machine restore UI.
"
}
function canonicalname() {
echo $(stat -f %R ${1})
}
############################################################################
## PARSE & MAKE SENSE OF COMMAND LINE ##
############################################################################
if [ ! $# -eq 1 ]; then
dispusage "Invalid number of arguments" && exit
fi
# Try to get canonical name
DATEDIRNAME=$(stat -f %R ${1})
if [[ "${DATEDIRNAME}" == "" ]]; then
dispusage "Could not get canonical name of directory ${1}" && exit
fi
# Ensure it's a directory
if [ ! -d "${DATEDIRNAME}" ]; then
dispusage "${DATEDIRNAME} is not a valid directory" && exit
fi
# Ensure it's a Backups.backupdb dir's subdir's subdir
if [[ ! "${DATEDIRNAME}" =~ ^.*\/Backups\.backupdb\/.*\/.*$ ]]; then
dispusage "${DATEDIRNAME} does not appear to be in a Backups.backupdb directory" && exit
fi
# Get the basename and ensure it's in a Time Machine timestamp format
DATEDIRBASENAME=$(basename "${DATEDIRNAME}")
if [[ ! "${DATEDIRBASENAME}" =~ ^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]\/?$ ]]; then
dispusage "${DATEDIRBASENAME} does not appear to be a snapshot directory" && exit
fi
# Get the Unix timestamp from the directory's Time Machine timestamp format
TIMESTAMP=$(date -j -f "%Y-%m-%d-%H%M%S" "${DATEDIRBASENAME}" +"%s")
TIMESTAMP="${TIMESTAMP}100000"
if [[ ! "${TIMESTAMP}" =~ ^[0-9]*$ ]]; then
dispusage "Could not automatically set snapshot timestamp due to folder name format" && exit
fi
if [ -d "${DATEDIRNAME}/Macintosh HD - Data" ]; then
DRVDIRNAME="${DATEDIRNAME}/Macintosh HD - Data"
elif [ -d "${DATEDIRNAME}/Macintosh HD" ]; then
DRVDIRNAME="${DATEDIRNAME}/Macintosh HD"
else
## TODO: Take the output of `ls -d ${DATEDIRNAME}` and try to autodetect?
dispusage "Could not find a volume name within ${DATEDIRNAME}" && exit
fi
############################################################################
## GET NECESSARY SYSTEM INFORMATION ##
############################################################################
VOLGRPUUID=`diskutil info / | awk -F' ' '/^ APFS Volume Group/{print $(NF)}'`
VOLDSKUUID=`diskutil info / | awk -F' ' '/^ Volume UUID/{print $(NF)}'`
if [ ! VOLGRPUUID == "" ]; then
TGTUUID="${VOLGRPUUID}"
elif [ ! VOLDSKUUID == "" ]; then
TGTUUID="${VOLDSKUUID}"
fi
KERNELVER=`uname -a | sed 's/.*Version \([0-9][0-9]*\).*/\1/g'`
if [ $KERNELVER -lt 20 ]; then
SIMONSAYS="sudo /System/Library/Extensions/TMSafetyNet.kext/Contents/Helpers/bypass"
else
SIMONSAYS="sudo"
fi
############################################################################
## CONFIRM ACTION INFORMATION ##
############################################################################
printf "\n\
Snapshot Directory: %s\n\
Snapshot Volume: %s\n\
Computer Volume: %s\n\
Volume Group: %s\n\\n" "${DATEDIRNAME}" "${DRVDIRNAME}" "${VOLDSKUUID}" "${VOLGRPUUID}"
printf "\
Preparing to run the following commands:\n\
%s xattr -c \"%s\"\n\
%s xattr -c \"%s\"\n\
%s xattr -w \"com.apple.backupd.SnapshotCompletionDate\" \"%s\" \"%s\"\n\
%s xattr -w \"com.apple.backupd.SnapshotState\" %s \"%s\"\n\
%s xattr -w \"com.apple.backupd.SnapshotVolumeUUID\" \"%s\" \"%s\"\n\
\n" "${SIMONSAYS}" "${DATEDIRNAME}" \
"${SIMONSAYS}" "${DRVDIRNAME}" \
"${SIMONSAYS}" "${TIMESTAMP}" "${DATEDIRNAME}" \
"${SIMONSAYS}" "4" "${DATEDIRNAME}" \
"${SIMONSAYS}" "${TGTUUID}" "${DRVDIRNAME}"
printf "Does everything look right?\n"
select response in "Bless Time Machine Snapshot" "ABORT ABORT ABORT!"; do
if [ "${response}" == "Bless Time Machine Snapshot" ]; then
"${SIMONSAYS}" xattr -c "${DATEDIRNAME}" && \
"${SIMONSAYS}" xattr -c "${DRVDIRNAME}" && \
"${SIMONSAYS}" xattr -w "com.apple.backupd.SnapshotCompletionDate" "${TIMESTAMP}" "${DATEDIRNAME}" && \
"${SIMONSAYS}" xattr -w "com.apple.backupd.SnapshotState" "4" "${DATEDIRNAME}" && \
"${SIMONSAYS}" xattr -w "com.apple.backupd.SnapshotVolumeUUID" "${TGTUUID}" "${DRVDIRNAME}"
if [ ! $? -eq 0 ]; then
printf "\nOperation failed.\n\n"
else
printf "\nOperation completed.\n\n"
printf "The snapshot directory now has the following metadata:\n"
printf "%s\n" "——————————————————————————————————————————————————————"
xattr -lv "${DATEDIRNAME}"
printf "\n\n"
printf "The snapshot volume now has the following metadata:\n"
printf "%s\n" "———————————————————————————————————————————————————"
xattr -lv "${DRVDIRNAME}"
printf "\n\n"
fi
break
else
printf "\nOperation aborted. No action has been taken.\n\n"
break
fi
done