homelab-blueprint/docs/unas/in-pool-reflink-move.md
2026-06-18 21:44:23 +02:00

3.8 KiB

U-NAS In-Pool Share Move

Use this procedure to move a large folder between UniFi Drive shares in the same Btrfs storage pool without temporarily duplicating all its data.

This uses Btrfs reflinks and metadata-only validation. It does not checksum file contents. Stop all writers first and keep a separate backup of important data.

1. Set the Paths

UniFi Drive user data is below each share's .data directory. Never operate on the share-level .trashcan, .uploads, .snapshot, or other service folders.

BASE='/volume/POOL_UUID/.srv/.unifi-drive'
SRC="$BASE/SOURCE_SHARE/.data/PATH_TO_FOLDER"
DST="$BASE/DESTINATION_SHARE/.data/FOLDER_NAME"

Confirm the paths before continuing:

printf 'SOURCE:      %s\nDESTINATION: %s\n' "$SRC" "$DST"
test -d "$SRC" || { echo 'ERROR: source does not exist'; false; }
test ! -e "$DST" || { echo 'ERROR: destination already exists'; false; }
du -sh "$SRC"
df -hT "$SRC" "$(dirname "$DST")"
fuser -vm "$SRC"

Stop applications, containers, SMB/NFS clients, downloaders, and scanners that may modify the source. Proceed only when the destination is absent and the source is idle.

Select a test file and clone it into the destination share:

SAMPLE="$(find "$SRC" -type f -size +10M -print -quit)"
REFTEST="$(dirname "$DST")/.reflink-test-$$"

cp --reflink=always --preserve=all -- "$SAMPLE" "$REFTEST"
btrfs filesystem du -s -- "$SAMPLE" "$REFTEST"
rm -- "$REFTEST"

Continue only if cp succeeds and btrfs filesystem du reports the test files as shared data with little or no exclusive allocation.

Start a persistent session:

tmux new-session -s unas-move

Recreate BASE, SRC, and DST inside tmux, then run:

mkdir -- "$DST"

cp -a \
  --reflink=always \
  --one-file-system \
  -- "$SRC/." "$DST/" \
  2>/root/unas-reflink-errors.log

CP_RC=$?
echo "cp exit code: $CP_RC"

Detach with Ctrl-B, then D. Reattach with:

tmux attach-session -t unas-move

If interrupted, rerun the same cp command. Do not continue unless its exit code is 0 and the error log is empty:

cat /root/unas-reflink-errors.log
btrfs filesystem du -s -- "$SRC" "$DST"
sync

4. Compare Metadata

Create type, path, size, modification-time, and symlink-target manifests. This is much faster than checksumming file contents:

(cd "$SRC" && find . ! -type d -printf '%y\t%P\t%s\t%T@\t%l\n' | LC_ALL=C sort) \
  > /root/unas-source-manifest.txt

(cd "$DST" && find . ! -type d -printf '%y\t%P\t%s\t%T@\t%l\n' | LC_ALL=C sort) \
  > /root/unas-destination-manifest.txt

diff -u \
  /root/unas-source-manifest.txt \
  /root/unas-destination-manifest.txt

Do not continue if diff prints differences.

5. Remove Synchronized Source Files

rsync -aXHvi \
  --remove-source-files \
  --info=progress2,stats2 \
  --log-file=/root/unas-move.log \
  -- "$SRC/" "$DST/"

RSYNC_RC=$?
echo "rsync exit code: $RSYNC_RC"

This metadata comparison avoids reading all file contents. On the tested U-NAS rsync version, matching reflinked files are treated as synchronized and removed from the source even when the transferred-byte count is zero.

Continue only if the exit code is 0.

6. Clean Up and Verify

Confirm that no source files or links remain:

find "$SRC" -mindepth 1 ! -type d -print

If that command prints nothing, remove empty source directories:

find "$SRC" -depth -type d -empty -delete

Verify the result:

test ! -e "$SRC" && echo 'Source removed successfully'
test -d "$DST" && echo 'Destination retained successfully'
du -sh "$DST"

Confirm the destination through UniFi Drive or a normal share client before restarting applications. Never use rm -rf as a substitute for these checks.