44

I am looking to see if Linux can mount and read the files natively stored on a Time Capsule over a network share. Like this question, I am looking for something that replicates at least read-only function of hdiutil to attach and then mount a sparse bundle disk images.

The SMB mount is easy since the Time Capsule shares using both AFP and SMB, but I'm not so sure the sparse disk bundle can be mounted as the reconstituted HFS+ directory.

Bonus points for a working mount command or pointer to the appropriate package that parses this DMG format.

In case it's not clear - this is how the band files look to me when mounted from a Mac in Terminal and what I expect Linux to see without the ability to mount the actual file system that is encoded in a multitude of binary band files.

host:iMac.sparsebundle mike$ ls -la
total 24
drwxrwxrwx@     7 mike  staff      264 Jul  5 10:01 .
drwx------      6 mike  staff      264 Mar 26 13:11 ..
-rwxrwxrwx      1 mike  staff      499 Feb 24 15:33 Info.bckup
-rwxrwxrwx      1 mike  staff      499 Feb 24 15:33 Info.plist
drwxrwxrwx  31101 mike  staff  1057390 Jun 17 20:19 bands
-rwxrwxrwx      1 mike  staff      532 Jun 24 22:06 com.apple.TimeMachine.MachineID.plist
-rwxrwxrwx      1 mike  staff        0 Feb 24 15:33 token
host:iMac.sparsebundle mike$ ls -la bands | head -10
total 1582092552
-rwxrwxrwx  1 mike  staff  8388608 Jul  5 08:33 0
-rwxrwxrwx  1 mike  staff  8388608 May 31 13:02 1
-rwxrwxrwx  1 mike  staff  8388608 Jun 24 22:16 10
-rwxrwxrwx  1 mike  staff  8388608 Mar 19 17:15 1000
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 10000
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 10001
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 10002
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 10003
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 10004
host:iMac.sparsebundle mike$ ls -la bands | tail -10
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:51 fff6
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:51 fff7
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:51 fff8
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:51 fff9
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:51 fffa
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 fffb
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 fffc
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 fffd
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 fffe
-rwxrwxrwx  1 mike  staff  8388608 May 31 00:50 ffff
host:~ mike$ ls -la bands|wc -l
   96636
bmike
  • 3,070

6 Answers6

43

You may use a combination of these two:

FUSE filesystem for reading Mac OS sparse-bundle disk images

Apple's Time Machine fuse read only file system

The first takes care of the .sparsebundle format, presenting it as a dmg file, which can then be mounted like normal. The second takes care of the directory hard-links used by Time Machine.

7

This is an extension to the answer by @TorArneVestbø.

Once you have installed https://github.com/torarnv/sparsebundlefs and https://github.com/abique/tmfs you need to run the following script in Bash. Make sure to update the two variables at the beginning to be the source and destination.

SB="/path/to/your/Backup.sparsebundle"
TM_MNT="/path/to/where/to/mount"

# Make directories
mkdir -p "$TM_MNT"
SB_MNT=`mktemp --tmpdir -d sparsebundle_mnt.XXX`
SB_DMG="$SB_MNT/sparsebundle.dmg"
HFS_MNT=`mktemp --tmpdir -d hfsx_mnt.XXX`

# Mount the sparse bundle
sudo `which sparsebundlefs` "$SB" "$SB_MNT"

# Mount the HFS+ partition
OFF=`sudo parted "$SB_DMG" unit B print | tr 'B' ' ' | awk '/hfsx/ {print $2}'`
SZ=`sudo parted "$SB_DMG" unit B print | tr 'B' ' ' | awk '/hfsx/ {print $4}'`
LO=`sudo losetup -f "$SB_DMG" --offset $OFF --sizelimit $SZ --show`
sudo mount -t hfsplus -r "$LO" "$HFS_MNT"

# Mount the Time Machine filesystem
sudo `which tmfs` "$HFS_MNT" "$TM_MNT" -ouid=$(id -u $USER),gid=$(id -g $USER),allow_other

The final mount will be accessible by you (as long as $TM_MNT is accessible to you). The final line may fail if FUSE is not setup to allow other user, it tells you how to fix it.

To unmount you need to do the following:

sudo umount "$TM_MNT"
sudo rmdir "$TM_MNT"
sudo umount "$HFS_MNT"
sudo rmdir "$HFS_MNT"
sudo losetup -d "$LO"
sudo umount "$SB_MNT"
sudo rmdir "$SB_MNT"

This was tested on a Fedora 28 system and is working well.

thaimin
  • 383
  • 1
  • 5
  • 13
5

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
Edward
  • 174
3

Apple's Time Machine fuse read only file system

https://github.com/abique/tmfs

tomislav
  • 149
3

The above post, from Alexandre Bicque, provides a Linux (?unix) program that will open a Time Machine sparsebundle stored on a Mac-formatted HFS+ disk or disk partition, allowing reading of the files on a Linux server.

Getting it set up isn't for the faint-hearted. It's written in C++ and requires 3 C++ libraries - cmake, FUSE, and Boost, with certain minimum versions (which may not be default latest versions for my Ubuntu Server 10.04.) It also requires finding and installing a g++ compiler and the above libraries.

I use Ubuntu server 10.04 and am not much of a programmer. However, after a fair bit of work and time, I did manage to install all the necessary libraries, compile and link the tmfs package, and use it. It does work, allowing mounting a TimeMachine Time Capsule. HOWEVER, it does require that the disk on which the sparsebundle image is written be an HFS+ disk or partition. It won't work if the image is written on an NTFS or ext2/ext3/ext4 file system on a Linux server.

As of Apple's OS X 10.7 (Lion), Time Machine (sparsebundle) images will no longer work if mounted on a Windows (smb/Samba) Linux share, and it's necessary to run Linux/Unix Netatalk (afpd plus avahi-daemon) services to use Linux as a Time Machine server.

I've done a lot of looking for another solution. I suspect that a Linux/Unix C++ programmer could do better than have I, extending Alexandre Bicque's work to allow the use of ext4 or ntfs file systems. I'm trying to figure out how to do it, but have a long way to go.

I think it will require that I understand much better the fuse (user-space file system) and perhaps the boost::filesystem system development helpers in order to move forward.

slhck
  • 235,242
2

Unfortunately the path to finding things in a sparsebundle from Linux is not straightforward. It can be done, but it requires interpreting some inode information that Apple embeds in the hardlinks to find the actual file in the sparsebundle. This MacWorld hint describes how you go about figuring out where a hardlink in a sparsebundle points to in terms of the actual file so you can access it from a Linux system. It deals with a Time Machine disk that's been attached as a local disk to a single machine.

In your case <mount point>/Backups.backupdb is most likely <machinename>.backupdb`.

I'm not sure whether <mount point>/.HFS+ Private Directory Data exists in the same spot for a shared disk being used for Time Machine backups by multiple machines. You'll have to do a little ls -la inspection of the disk and sparsebundles to find that.

But otherwise those MacWorld instructions will help you retrieve files on a Time Machine bundle, from Linux.

A update regarding the mount point.

I did some experimenting based on your updated question. It looks like the mount point should be the *.sparsebundle directory and not the drive. If I mount the drive in OS X and the go to /Volumes/Remote Backups/mymachine.sparsebundle I see the bands directory like you do and it's useless.

But if I mount mymachine.sparsebundle such that I can go to /Volumes/Time Machine Backups (that's what it mounts as automatically in Finder when I double click on the mymachine.sparsebundle) I see the expected Backups.backupdb directory and under that the date-time directories as expected.

Ian C.
  • 6,209