Newer MacOS versions don't use HFS+ as an underlying FS for Time Machine's sparsebundle, but uses APFS instead. The advantage of APFS is that it natively (as in: inside the FS itself) supports snapshots.
Here is a complete tutorial, tested and working on Fedora Workstation 33, on how you can mount a MacOS Time Machine backup folder on SMB, then mount the sparsebundle, then mount the APFS filesystem and access all snapshots.
How to mount a Mac Time Machine backup sparsebundle directory (on SMB) as a browseable filesystem on Linux
This guide was tested on Fedora 33.
Prerequisites
We need sparsebundlefs
to mount a sparsebundle directory as a DMG disk image. Also, we need APFS fuse driver and user space utilities to mount the Mac APFS partition inside the DMG file. With the apfsutil
binary, we can even browse the snapshots inside the APFS partition.
# we assume the directory /usr/local/bin exists and is in the path
# become root
sudo su
# install sparsebundlefs (https://github.com/torarnv/sparsebundlefs)
yum -y install fuse-devel
cd
git clone https://github.com/torarnv/sparsebundlefs.git
cd sparsebundlefs
make
mv sparsebundlefs /usr/local/bin
# install apfs-fuse (https://github.com/sgan81/apfs-fuse, https://linuxnewbieguide.org/how-to-mount-macos-apfs-disk-volumes-in-linux/)
yum -y install bzip2-devel fuse3-devel
cd
git clone https://github.com/sgan81/apfs-fuse.git
cd apfs-fuse
git submodule init
git submodule update
mkdir build
cd build
cmake ..
make
mv apfs* /usr/local/bin
ln -s /usr/local/bin/apfs-fuse /usr/sbin/mount.apfs
Script
Create a file mount_timebackup.sh
containing the following. Don't forget to chmod +x
.
#!/usr/bin/env bash
# mount_timebackup.sh
## CHANGE VARIABLES HERE
SMB_PATH="\\\\server_name_or_ip_address/share"
SMB_USERNAME="some_username"
SMB_PASSWORD="some_password"
## --END--
SMB_MNT="/mnt/timebackup_sparsebundle"
SMB_OPTIONS="user=${SMB_USERNAME},pass=${SMB_PASSWORD},ro"
SB_MNT="/mnt/sparsebundlefs"
SB_DMG="${SB_MNT}/sparsebundle.dmg"
APFS_MNT="/mnt/apfs"
# Check availability of binaries
SBFS_BIN="$(which sparsebundlefs)"
if [[ -z "${SBFS_BIN}" ]]; then echo "[!] sparsebundlefs binary not found, aborting."; exit 1; fi
PARTED_BIN="$(which parted)"
if [[ -z "${PARTED_BIN}" ]]; then echo "[!] parted binary not found, aborting."; exit 1; fi
LOSETUP_BIN="$(which losetup)"
if [[ -z "${LOSETUP_BIN}" ]]; then echo "[!] losetup binary not found, aborting."; exit 1; fi
APFSUTIL_BIN="$(which apfsutil)"
if [[ -z "${APFSUTIL_BIN}" ]]; then echo "[!] apfsutil binary not found, aborting."; exit 1; fi
# Make directories
mkdir -p "${SMB_MNT}" 2>/dev/null
mkdir -p "${SB_MNT}" 2>/dev/null
mkdir -p "${APFS_MNT}" 2>/dev/null
# Mount SMB share
if ! grep -q ${SMB_MNT} /proc/mounts
then
echo "[i] Mounting share \"${SMB_PATH}\" as user ${SMB_USERNAME} on ${SMB_MNT}..."
if ! mount.cifs "${SMB_PATH}" "${SMB_MNT}" -o "${SMB_OPTIONS}"
then
echo "[!] Error mounting SMB share, check output. Aborting."
exit 1
fi
else
echo "[i] SMB share ${SMB_PATH} found on ${SMB_MNT}..."
fi
# Mount the sparse bundle
if ! grep -q ${SB_MNT} /proc/mounts
then
echo "[i] Finding sparsebundle directory..."
SB="$(find "${SMB_MNT}" -maxdepth 1 -type d -name '*.sparsebundle')"
if [[ -z "${SB}" ]]
then
echo "[!] Sparsebundle directory not found under $SMB_MNT, aborting."
exit 1
fi
echo "[i] Mounting sparsebundle directory \"${SB}\" as sparsebundle filesystem on ${SB_MNT}..."
if ! "${SBFS_BIN}" "$SB" "$SB_MNT"
then
echo "[!] Error mounting sparsebundlefs, check output. Aborting."
exit 1
fi
else
echo "[i] Sparsebundle mount found on ${SB_MNT}..."
fi
# Mount the APFS partition as loopback device
LO="$("${LOSETUP_BIN}" | grep "${SB_MNT}" | awk '{print $1}')"
if [[ -z "${LO}" ]]
then
echo "[i] Determining characteristics of APFS filesystem inside ${SB_DMG}..."
OFF="$("${PARTED_BIN}" "${SB_DMG}" unit B print 2>/dev/null | tr 'B' ' ' | awk '/disk image/ {print $2}')"
SZ="$("${PARTED_BIN}" "${SB_DMG}" unit B print 2>/dev/null | tr 'B' ' ' | awk '/disk image/ {print $4}')"
if [[ -z "${OFF}" ]] || [[ -z "${SZ}" ]]
then
echo "[!] Unable to determine APFS filesystem offset and size characteristics, aborting."
exit 1
fi
echo "Mounting APFS filesystem inside ${SB_DMG} from offset ${OFF} with max size ${SZ} on loopback device..."
LO="$("${LOSETUP_BIN}" -f "${SB_DMG}" --offset ${OFF} --sizelimit ${SZ} --show)"
if [[ -z "${LO}" ]]
then
echo "[!] Error mounting APFS filesystem, aborting."
exit 1
fi
else
echo "[i] APFS filesystem found at ${LO}."
fi
# List snapshots
echo "[i] Listing available snapshots in the APFS filesystem at ${LO}:"
"${APFSUTIL_BIN}" "${LO}"
echo "[i] To mount the latest available snapshot, run:"
echo " mount.apfs \"${LO}\" \"${APFS_MNT}\""
echo
echo "[i] To mount a specific snapshot, run:"
echo " mount.apfs -o snap=XXXXX \"${LO}\" \"${APFS_MNT}\""
Usage
Edit the mount_timebackup.sh
file and put in the SMB path, username and password.
Then, run the script. It will show something like this:
14:07 ★root(su)@fedora /root/bin
0» ./mount_timebackup.sh
[i] Mounting share "\\192.168.1.1/My_Timebackup" as user edward on /mnt/timebackup_sparsebundle...
[i] Finding sparsebundle directory...
[i] Mounting sparsebundle directory "/mnt/timebackup_sparsebundle/edward.sparsebundle" as sparsebundle filesystem on /mnt/sparsebundlefs...
[i] Determining characteristics of APFS filesystem inside /mnt/sparsebundlefs/sparsebundle.dmg...
Mounting APFS filesystem inside /mnt/sparsebundlefs/sparsebundle.dmg from offset 209735680 with max size 22685610287104 on loopback device...
[i] Listing available snapshots in the APFS filesystem at /dev/loop0:
Volume 0 B2853571-BC03-4DCC-91B7-295D046776BF
---------------------------------------------
Role: Backup
Name: Reservekopieën van Edward (Case-sensitive)
Capacity Consumed: 177614307328 Bytes
FileVault: No
Snapshots:
587 : 'com.apple.TimeMachine.2021-05-27-131216.backup'
3122 : 'com.apple.TimeMachine.2021-06-03-142024.backup'
5354 : 'com.apple.TimeMachine.2021-06-10-185257.backup'
7193 : 'com.apple.TimeMachine.2021-06-17-210159.backup'
8278 : 'com.apple.TimeMachine.2021-06-24-135457.backup'
10556 : 'com.apple.TimeMachine.2021-07-01-221410.backup'
12345 : 'com.apple.TimeMachine.2021-07-09-155806.backup'
13326 : 'com.apple.TimeMachine.2021-07-17-074435.backup'
13747 : 'com.apple.TimeMachine.2021-07-24-112952.backup'
16670 : 'com.apple.TimeMachine.2021-07-31-085515.backup'
17164 : 'com.apple.TimeMachine.2021-08-25-112049.backup'
19466 : 'com.apple.TimeMachine.2021-09-01-202809.backup'
21613 : 'com.apple.TimeMachine.2021-09-08-200654.backup'
24023 : 'com.apple.TimeMachine.2021-09-15-202105.backup'
25807 : 'com.apple.TimeMachine.2021-09-23-194219.backup'
27372 : 'com.apple.TimeMachine.2021-09-30-161110.backup'
29229 : 'com.apple.TimeMachine.2021-10-08-064900.backup'
30021 : 'com.apple.TimeMachine.2021-10-14-222820.backup'
30260 : 'com.apple.TimeMachine.2021-10-22-132638.backup'
33163 : 'com.apple.TimeMachine.2021-10-30-141729.backup'
36728 : 'com.apple.TimeMachine.2021-11-06-102305.backup'
40140 : 'com.apple.TimeMachine.2021-11-13-201741.backup'
41362 : 'com.apple.TimeMachine.2021-11-21-100951.backup'
42977 : 'com.apple.TimeMachine.2021-11-28-212032.backup'
45488 : 'com.apple.TimeMachine.2021-12-06-155539.backup'
48657 : 'com.apple.TimeMachine.2021-12-13-154439.backup'
50554 : 'com.apple.TimeMachine.2021-12-20-084732.backup'
51856 : 'com.apple.TimeMachine.2022-02-06-105522.backup'
54178 : 'com.apple.TimeMachine.2022-02-13-215427.backup'
57082 : 'com.apple.TimeMachine.2022-02-21-151624.backup'
58494 : 'com.apple.TimeMachine.2022-03-06-155624.backup'
60265 : 'com.apple.TimeMachine.2022-03-13-124536.backup'
64198 : 'com.apple.TimeMachine.2022-03-20-160040.backup'
67262 : 'com.apple.TimeMachine.2022-03-25-173848.backup'
68136 : 'com.apple.TimeMachine.2022-03-26-233827.backup'
69024 : 'com.apple.TimeMachine.2022-03-28-165918.backup'
69886 : 'com.apple.TimeMachine.2022-03-29-164136.backup'
70515 : 'com.apple.TimeMachine.2022-03-30-194139.backup'
70869 : 'com.apple.TimeMachine.2022-04-01-014510.backup'
72140 : 'com.apple.TimeMachine.2022-04-02-082134.backup'
72965 : 'com.apple.TimeMachine.2022-04-08-204109.backup'
73092 : 'com.apple.TimeMachine.2022-04-10-194926.backup'
73745 : 'com.apple.TimeMachine.2022-04-11-204535.backup'
74595 : 'com.apple.TimeMachine.2022-04-12-204033.backup'
75319 : 'com.apple.TimeMachine.2022-04-13-190833.backup'
75971 : 'com.apple.TimeMachine.2022-04-14-222526.backup'
77237 : 'com.apple.TimeMachine.2022-04-16-083710.backup'
77319 : 'com.apple.TimeMachine.2022-04-17-094739.backup'
77619 : 'com.apple.TimeMachine.2022-04-18-090536.backup'
77705 : 'com.apple.TimeMachine.2022-04-18-104921.backup'
77825 : 'com.apple.TimeMachine.2022-04-19-225314.backup'
78369 : 'com.apple.TimeMachine.2022-04-20-171232.backup'
78469 : 'com.apple.TimeMachine.2022-04-20-190918.backup'
78598 : 'com.apple.TimeMachine.2022-04-20-203829.backup'
78668 : 'com.apple.TimeMachine.2022-04-20-220645.backup'
78794 : 'com.apple.TimeMachine.2022-04-21-155554.backup'
79746 : 'com.apple.TimeMachine.2022-04-30-101539.backup'
79836 : 'com.apple.TimeMachine.2022-04-30-112852.backup'
79863 : 'com.apple.TimeMachine.2022-04-30-114329.backup'
79937 : 'com.apple.TimeMachine.2022-04-30-122935.backup'
80049 : 'com.apple.TimeMachine.2022-05-01-083701.backup'
80131 : 'com.apple.TimeMachine.2022-05-01-093828.backup'
80253 : 'com.apple.TimeMachine.2022-05-01-110758.backup'
80289 : 'com.apple.TimeMachine.2022-05-01-122415.backup'
[i] To mount the latest available snapshot, run:
mount.apfs "/dev/loop0" "/mnt/apfs"
[i] To mount a specific snapshot, run:
mount.apfs -o snap=XXXXX "/dev/loop0" "/mnt/apfs"
Unmounting
# as root, in this specific order
umount /mnt/apfs
losetup -D
umount /mnt/sparsebundlefs
umount /mnt/timebackup_sparsebundle
cd
in to it once the volume where your Time Machine backups are being stored is mounted on Linux.