TT Lab
Get started
Learn Learning paths Courses

SSH and File Transfer

Synchronising With rsync

Continue in TT Lab

Goal

You confirm rsync's key options by hand. In particular you build three things yourself: the trailing slash rule, the danger and guard of --delete, and a --link-dest generational backup.

Why it matters

rsync is the tool used most and gotten wrong most. The fact that -a does not include ACLs, extended attributes, or hard links, the fact that one slash at the end of the source path changes the directory depth, and the fact that --delete wipes out the target when the source is empty — all three have led to real incidents. --link-dest, on the other hand, is rsync's most beautiful feature. Each generation looks like a full snapshot while the disk is occupied only by the changes.

Steps

  1. Create the following under /root/sync/src: app/main.py, app/util.py, conf/app.yaml, logs/app.log, and logs/error.log. Put at least one line of any content in each file.
  2. Run a dry-run of synchronizing /root/sync/src/ to /root/sync/dst1/, with the itemized list of changes shown, and save it to /root/sync/dryrun.txt.
  3. Actually perform the same synchronization over SSH. The target is root@127.0.0.1:/root/sync/dst1/, port 2222, and the key is /root/.ssh/id_labhub. After it completes, /root/sync/dst1/app/main.py must exist.
  4. Demonstrate the trailing slash difference. If you send /root/sync/src (no slash) to /root/sync/dst2/, /root/sync/dst2/src/app/main.py must appear.
  5. Write the script /root/sync/backup.sh. It must start with set -euo pipefail, include a check that aborts with exit code 1 if the source is empty, and synchronize /root/sync/src/ to /root/sync/dst3/ using --delete. The source path is taken as the first argument, and if there is no argument, it uses /root/sync/src/ — the grader passes an empty directory as the first argument to check that the guard really runs. Run the script to create /root/sync/dst3.
  6. Synchronize /root/sync/src/ to /root/sync/dst4/ excluding what is under logs/. /root/sync/dst4/logs must not exist.
  7. Make a generational backup. First do a full synchronization to /root/sync/gen/daily.1/, and then create /root/sync/gen/daily.0/ with --link-dest. For files that did not change, the two generations must share the same inode.
  8. Make /root/sync/final.txt with the following 3 lines. PENDING=<3번과 같은 동기화를 다시 dry-run 했을 때 전송 예정 파일 수> / DST4_HAS_LOGS=no / SHARED_INODE=yes (PENDING= is followed by the number of files that would be transferred when you dry-run the same synchronization as step 3 again) PENDING must be 0.

Notes

Creating the source tree

Create the following under /root/sync/src: app/main.py, app/util.py, conf/app.yaml, logs/app.log, and logs/error.log. Put at least one line of any content in each file.

If you mix subdirectories and log files, the exclude rule in a later step has meaning.

Simulate first

Run a dry-run of synchronizing /root/sync/src/ to /root/sync/dst1/, with the itemized list of changes shown, and save it to /root/sync/dryrun.txt.

Use together the option that only shows what would change without making actual changes and the option that shows the changes one per line.

Actual synchronization over SSH

Actually perform the same synchronization over SSH. The target is root@127.0.0.1:/root/sync/dst1/, port 2222, and the key is /root/.ssh/id_labhub. After it completes, /root/sync/dst1/app/main.py must exist.

rsync has an option that specifies the whole remote shell command. Put the port and key inside it.

Demonstrating the trailing slash difference

Demonstrate the trailing slash difference. If you send /root/sync/src (no slash) to /root/sync/dst2/, /root/sync/dst2/src/app/main.py must appear.

Send the same source to two targets, changing only the presence of the slash, and compare the resulting structures.

--delete with a guard

Write the script /root/sync/backup.sh. It must start with set -euo pipefail, include a check that aborts with exit code 1 if the source is empty, and synchronize /root/sync/src/ to /root/sync/dst3/ using --delete. The source path is taken as the first argument, and if there is no argument, it uses /root/sync/src/ — the grader passes an empty directory as the first argument to check that the guard really runs. Run the script to create /root/sync/dst3.

Put a check at the front of the script that aborts immediately if the source is empty. And set -euo pipefail too.

Applying an exclude rule

Synchronize /root/sync/src/ to /root/sync/dst4/ excluding what is under logs/. /root/sync/dst4/logs must not exist.

You can specify exclude patterns several times or read them from a file. The log files must not be in the target.

Hard-link generational backup

Make a generational backup. First do a full synchronization to /root/sync/gen/daily.1/, and then create /root/sync/gen/daily.0/ with --link-dest. For files that did not change, the two generations must share the same inode.

If you give --link-dest a relative path, it is interpreted relative to the destination. An absolute path is safe. Check with the link count of stat.

Proof that synchronization is complete

Make /root/sync/final.txt with the following 3 lines. PENDING=<3번과 같은 동기화를 다시 dry-run 했을 때 전송 예정 파일 수> / DST4_HAS_LOGS=no / SHARED_INODE=yes (PENDING= is followed by the number of files that would be transferred when you dry-run the same synchronization as step 3 again) PENDING must be 0.

If it was synchronized correctly, running the same command again as a dry-run should leave nothing to transfer.