From a577ae5199c912dafe306af37ccebbfb4a219ba3 Mon Sep 17 00:00:00 2001 From: Lee Ockert Date: Wed, 27 Jul 2022 11:06:40 -0400 Subject: [PATCH] Added a tmbless utility Added a utility to bless a snapshot directory --- README.md | 20 +++++++ tmbless.sh | 168 ++++++++++++++++++++++++++++++++++++++++++++++++++++ tmimport.sh | 4 +- 3 files changed, 190 insertions(+), 2 deletions(-) create mode 100755 tmbless.sh diff --git a/README.md b/README.md index c0410d9..6ceb4aa 100644 --- a/README.md +++ b/README.md @@ -59,6 +59,26 @@ ______________________________________________________________________________ ______________________________________________________________________________ +**tmbless** +------------ + TIME MACHINE BLESS + + USAGE + + tmbless.sh + + DESCRIPTION + + Time Machine Blessing modifies the metadata of a snapshot directory + (i.e., a datestamped directory inside a Backups.backupdb/machinename/ + directory) so that the metadata reflects a backup completed on that date + and the metadata of the top-level drive matches that of the current drive. + + These modifications should allow restoration of files within the Time + Machine restore UI. +______________________________________________________________________________ + + **dirdedupe** ------------- DIRECTORY DE-DUPLICATOR diff --git a/tmbless.sh b/tmbless.sh new file mode 100755 index 0000000..e4541b3 --- /dev/null +++ b/tmbless.sh @@ -0,0 +1,168 @@ +#!/usr/bin/env bash + +# BITSC LICENSE NOTICE (MODIFIED ISC LICENSE) +# +# TIME MACHINE BLESS +# +# Copyright (c) 2022 Lee Ockert +# https://github.com/torstenvl +# +# 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. + + +function dispusage() { + if [ ${#1} -gt 0 ]; then + printf "%s\n\n" "${1}" + fi + echo "\ +TIME MACHINE BLESS + +USAGE + + ${0} + +DESCRIPTION + + Time Machine Blessing modifies the metadata of a snapshot directory + (i.e., a datestamped directory inside a Backups.backupdb/machinename/ + directory) so that the metadata reflects a backup completed on that date + and the metadata of the top-level drive matches that of 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 diff --git a/tmimport.sh b/tmimport.sh index d1509ee..f39c481 100755 --- a/tmimport.sh +++ b/tmimport.sh @@ -141,7 +141,7 @@ Preparing to run the following commands:\n\ "${SIMONSAYS}" "${UUID}" "${BACKUPPATH}" \ "${SIMONSAYS}" "${BACKUPPATH}" -if [ ! "$(basename "${BACKUPPATH}")" -eq "$(scutil --get ComputerName)"]; then +if [ ! "$(basename "${BACKUPPATH}")" == "$(scutil --get ComputerName)" ]; then printf "#############################################################################\n" printf "## W A R N I N G W A R N I N G W A R N I N G ##\n" printf "#############################################################################\n" @@ -155,7 +155,7 @@ if [ ! "$(basename "${BACKUPPATH}")" -eq "$(scutil --get ComputerName)"]; then printf "Only proceed if you are very certain of what you're doing!\n" printf "Even if successful, the Time Machine restore UI will be adversely affected.\n" else - printf "Does everything look right?\n\n" + printf "Does everything look right?\n" fi