I created a backup script that should create incremental backups every day and a full backup once a week from a remote host to my external backup hard drive.

(short example at the bottom)

The script (https://gist.github.com/sergei-maertens/ac6b8ee2562edcff10fc):

#!/bin/env bash

# Adapted from http://webgnuru.com/linux/rsync_incremental.php

DAYS_BETWEEN_FULL=30  # number of days between full backups.


today=`date -I`
prev_day=$today  # check if backup from today exists

get_target() {
    if [[ $2 ]]; then
    echo $root_dir

LOG=`get_target $today 'rsync_inc.log'`

# do the date calculations
# determine if a full/incremental backup should be taken
run_backup() {
    PREV_BACKUP=`get_target $prev_day`

    while [ $i -lt 100 ] && [ ! -d $PREV_BACKUP ]
        prev_day=`date -I -d "$i day ago"`
        PREV_BACKUP=`get_target $prev_day`

    if [ $i -eq '0' ]; then
        echo "Backup already created"
        # exit
        echo $i 'days since last backup'

    # create the target directory
    TARGET=`get_target $today`
    echo -e 'Backup target:' $TARGET "\n"
    mkdir -p $TARGET

    if [ $i -lt $DAYS_BETWEEN_FULL ]; then
        echo -e "creating incremental backup, based on" $prev_day "\n"
        echo -e "creating full backup \n"
        source ./backup.sh

incremental_backup() {
    DEST=`get_target $today $TARGET_SUFFIX`
    COMP=`get_target $prev_day $TARGET_SUFFIX`
    echo 'remote source directory:' $SOURCE
    echo 'backup destination:' $DEST

    SSH="-e ssh $USER@$HOST:$SOURCE"

    echo $LOG
    rsync $OPT --link-dest=$COMP $SSH $TARGET > $LOG


What it's supposed to do Basically what it does it create a target folder for the backup with today's date, look for the previous backup (subtract one day from the previous date until a folder was found, since I cannot run the backup every day/night) and if more than 7 days elapsed, a full backup will be created.

This backup is quite large, so I want to use --link-dest to create hard links to the previous backup if the file hasn't changed.

The remote host is accessed with passwordless ssh, nothing special going on here.

What it does

The observed behaviour is that the backup is indeed created correctly, but no hard links are created, even though files appear to be identical.

Potential useful information

Some more possibly related information:

  • the remote host filesystem is simfs, OS is CentOS 5.11
  • the backup drive filesystem is ext4, running from Arch Linux machine
  • file mtimes match (if I interpret the stat output correctly)
  • both machines are in the same timezone
  • local machine rsync version 3.1.1
  • remote machine rsync version 3.0.6

What else did I try

I ran rsync with the same options and via ssh on my local machine with the folders /home/bbt/test/source, /home/bbt/test/prev, /home/bbt/test/target. The target folder files do indeed have hard links to the prev folder files, as expected.

Output from currently running backup

➜  scripts  ./run_backup.sh
2 days since last backup
Backup target: /run/media/bbt/40d67de8-f4b6-4707-9947-f96f44efd7bf/2015-10-24 

creating incremental backup, based on 2015-10-22 

remote source directory: /home/modelbrouw/django/production/media
backup destination: /run/media/bbt/40d67de8-f4b6-4707-9947-f96f44efd7bf/2015-10-24/media

Then I went to verify a test file:

    ➜  1  pwd
➜  1  stat 1024_chameleon.jpg 
  File: ‘1024_chameleon.jpg’
  Size: 31694       Blocks: 64         IO Block: 4096   regular file
Device: 811h/2065d  Inode: 81264905    Links: 1
Access: (0645/-rw-r--r-x)  Uid: ( 1000/     bbt)   Gid: ( 1000/     bbt)
Access: 2015-10-22 20:32:09.550197848 +0200
Modify: 2012-07-04 19:13:10.000000000 +0200
Change: 2015-10-22 20:32:09.550197848 +0200
 Birth: -

And check the corresponding file in the new backup - here I would expect a hard link:

➜  1  pwd
➜  1  stat 1024_chameleon.jpg 
  File: ‘1024_chameleon.jpg’
  Size: 31694       Blocks: 64         IO Block: 4096   regular file
Device: 811h/2065d  Inode: 107348180   Links: 1
Access: (0645/-rw-r--r-x)  Uid: ( 1000/     bbt)   Gid: ( 1000/     bbt)
Access: 2015-10-24 17:07:01.695760211 +0200
Modify: 2012-07-04 19:13:10.000000000 +0200
Change: 2015-10-24 17:00:34.284219203 +0200
 Birth: -

Finally, I log all rsync output as well, the head of the file looks like this:

➜  2015-10-24  head rsync_inc.log -n 100
receiving incremental file list
             49 100%   47.85kB/s    0:00:00 (xfr#1, ir-chk=1114/1116)
         74.35K 100%   77.74kB/s    0:00:00 (xfr#2, ir-chk=1276/2436)
         81.28K 100%   82.26kB/s    0:00:00 (xfr#3, ir-chk=1275/2436)
         59.76K 100%   59.68kB/s    0:00:00 (xfr#4, ir-chk=1274/2436)
        100.46K 100%   97.32kB/s    0:00:01 (xfr#5, ir-chk=1273/2436)
        105.82K 100%    3.15MB/s    0:00:00 (xfr#6, ir-chk=1272/2436)
         82.08K 100%    1.53MB/s    0:00:00 (xfr#7, ir-chk=1271/2436)
         31.69K 100%  562.75kB/s    0:00:00 (xfr#8, ir-chk=1270/2436)
         49.58K 100%  691.70kB/s    0:00:00 (xfr#9, ir-chk=1269/2436)
         55.32K 100%  675.32kB/s    0:00:00 (xfr#10, ir-chk=1268/2436)
         41.20K 100%  410.56kB/s    0:00:00 (xfr#11, ir-chk=1267/2436)
         67.87K 100%  597.15kB/s    0:00:00 (xfr#12, ir-chk=1266/2436)
         69.35K 100%  559.73kB/s    0:00:00 (xfr#13, ir-chk=1265/2436)
         56.50K 100%  396.95kB/s    0:00:00 (xfr#14, ir-chk=1264/2436)
         20.77K 100%  145.92kB/s    0:00:00 (xfr#15, ir-chk=1263/2436)
          2.41K 100%   16.82kB/s    0:00:00 (xfr#16, ir-chk=1262/2436)
          2.57K 100%   17.95kB/s    0:00:00 (xfr#17, ir-chk=1261/2436)
          2.37K 100%   16.50kB/s    0:00:00 (xfr#18, ir-chk=1260/2436)
          3.21K 100%   21.33kB/s    0:00:00 (xfr#19, ir-chk=1259/2436)
          2.94K 100%   19.56kB/s    0:00:00 (xfr#20, ir-chk=1258/2436)
          2.91K 100%   19.31kB/s    0:00:00 (xfr#21, ir-chk=1257/2436)
          1.45K 100%    9.60kB/s    0:00:00 (xfr#22, ir-chk=1256/2436)
media/albums/1/14/2012-07-28 20.46.38.jpg
         89.52K 100%  526.65kB/s    0:00:00 (xfr#23, ir-chk=1255/2436)
media/albums/1/14/2012-07-28 20.46.50.jpg
         99.56K 100%  522.70kB/s    0:00:00 (xfr#24, ir-chk=1254/2436)
media/albums/1/14/2012-07-28 20.47.49.jpg
        121.80K 100%  553.25kB/s    0:00:00 (xfr#25, ir-chk=1253/2436)
media/albums/1/14/2012-07-28 20.47.59.jpg
        117.66K 100%  476.78kB/s    0:00:00 (xfr#26, ir-chk=1252/2436)
media/albums/1/14/2012-07-28 20.48.06.jpg
        101.82K 100%  380.98kB/s    0:00:00 (xfr#27, ir-chk=1251/2436)
media/albums/1/14/thumb_2012-07-28 20.46.38.jpg
          6.99K 100%   26.16kB/s    0:00:00 (xfr#28, ir-chk=1250/2436)
media/albums/1/14/thumb_2012-07-28 20.46.50.jpg
          7.55K 100%   28.25kB/s    0:00:00 (xfr#29, ir-chk=1249/2436)
media/albums/1/14/thumb_2012-07-28 20.47.49.jpg
          7.13K 100%   25.98kB/s    0:00:00 (xfr#30, ir-chk=1248/2436)
media/albums/1/14/thumb_2012-07-28 20.47.59.jpg
          6.81K 100%   24.82kB/s    0:00:00 (xfr#31, ir-chk=1247/2436)
media/albums/1/14/thumb_2012-07-28 20.48.06.jpg
          7.33K 100%   26.72kB/s    0:00:00 (xfr#32, ir-chk=1246/2436)
media/albums/1/2/1024_2012-05-03 21.49.27.jpg
         56.56K 100%  197.97kB/s    0:00:00 (xfr#33, ir-chk=1245/2436)
media/albums/1/2/1024_2012-05-03 22.37.00.jpg
         53.89K 100%  167.60kB/s    0:00:00 (xfr#34, ir-chk=1244/2436)
media/albums/1/2/2012-05-03 21.49.27.jpg
         56.56K 100%  173.15kB/s    0:00:00 (xfr#35, ir-chk=1243/2436)
media/albums/1/2/2012-05-03 22.37.00.jpg
         53.89K 100%  158.99kB/s    0:00:00 (xfr#36, ir-chk=1242/2436)
media/albums/1/2/2012-05-03 22.53.39.jpg
         60.56K 100%  169.46kB/s    0:00:00 (xfr#37, ir-chk=1241/2436)
media/albums/1/2/thumb_2012-05-03 21.49.27.jpg
          2.44K 100%    6.80kB/s    0:00:00 (xfr#38, ir-chk=1240/2436)
media/albums/1/2/thumb_2012-05-03 22.37.00.jpg
          2.55K 100%    7.10kB/s    0:00:00 (xfr#39, ir-chk=1239/2436)
media/albums/1/2/thumb_2012-05-03 22.53.39.jpg
          2.62K 100%    7.31kB/s    0:00:00 (xfr#40, ir-chk=1238/2436)
        152.05K 100%  370.28kB/s    0:00:00 (xfr#41, ir-chk=1237/2436)
        162.87K 100%  363.13kB/s    0:00:00 (xfr#42, ir-chk=1236/2436)
        155.44K 100%  316.91kB/s    0:00:00 (xfr#43, ir-chk=1235/2436)
        147.72K 100%  275.30kB/s    0:00:00 (xfr#44, ir-chk=1234/2436)
        259.65K 100%  417.74kB/s    0:00:00 (xfr#45, ir-chk=1233/2436)
        171.33K 100%  260.20kB/s    0:00:00 (xfr#46, ir-chk=1232/2436)

The full log file from the previous backup (which ran with the same script) can be found at https://modelbrouwers.nl/static/rsync_inc.log

Short example

the command that gets run:

rsync -rvLtShP --link-dest=/backupdrive/2015-10-22/media -e ssh modelbrouw@modelbrouwers.nl:/home/modelbrouw/django/production/media /backupdrive/2015-10-24

In the backup target (2015-10-24) I would expect a hard link from /backupdrive/2015-10-24/media/albums/1/1/1024_chameleon.jpg to /backupdrive/2015-10-21/media/albums/1/1/1024_chameleon.jpg

... and as I'm writing this, I probably have to add the media to the target. I'll try that out first now.

  • Hello BBT and welcome to Unix.SE. Instead of dumping the entire script, can you provide a short example illustrating the problem you're describing, please. – roaima Oct 24 '15 at 15:48
  • 1
    thank you roaima I have edited the question and added a short example at the bottom. I also realize I might have found the issue, so I'll try that first. – BBT Oct 24 '15 at 15:55
  • 1
    Glad you found the problem. As a suggestion, I would recommend doing the rsync initially to a directory like 2015-10-24.tmp and then renaming it to 2015-10-24 at the end if the rsync exits with code 0. Otherwise, in case of error the previous day, you may have a directory that is only partially filled that will be used as the new link-dest, so you will be using up real file space again. Note there is also an rsnapshot command, though I've not used it. – meuh Oct 24 '15 at 16:34
  • That's a very good suggestion, thanks. I've heard of rsnapshot - we use it at work, but haven't looked into it yet. – BBT Oct 24 '15 at 17:17
  • 1
    BTW, I don't recall ever seeing a system that had bash, where it wasn't available as /bin/bash, but I've definitely seen systems that have env, where it isn't available as /bin/env.  The standard location for env is /usr/bin/env, so if you're going to use it in your she-bang line (or reference it by full path for any other reason), you should use that.  But #!/bin/bash is probably safe. – G-Man Says 'Reinstate Monica' Oct 25 '15 at 2:04

The trailing slash + missing part of path bit me, typing out the short example helped.

The solution is that the trailing slash must be added to the $SOURCE and media needs to be added to the eventual $TARGET.

| improve this answer | |

Not the answer you're looking for? Browse other questions tagged or ask your own question.