summaryrefslogtreecommitdiff
path: root/rsyncshot
blob: 5b0112958b5e5cc303a2cb97ea2be99ae4d1dab4 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
#!/bin/bash
# rsyncshot

# Craig Jennings craigmartinjennings@gmail.com
# Inspired by Mike Rubel: http://www.mikerubel.org/computers/rsync_snapshots/

# Debugging

# uncomment next 4 lines for debugging output
# exec 5> >(logger -t $0)
# BASH_XTRACEFD="5"
# PS4='$LINENO: '
# set -x

# Default Locations For Setup

SCRIPTLOC=/usr/local/bin/rsyncshot;
DESTINATION=/media/backup;

INSTALLHOME=/etc/rsyncshot
LOGHOME=/var/log/rsyncshot.log;

INCLUDES="$INSTALLHOME/include.txt";
EXCLUDES="$INSTALLHOME/exclude.txt";

# Default Cron Job Entries
# CRON_H = hourly on minute 0 from 1am to 11pm
# CRON_D = daily at midnight, Monday - Saturday
# CRON_W = weekly at midnight on Sundays

CRON_H="0 1-23 * * * $SCRIPTLOC hourly 22";
CRON_D="0 0 * * 1-6 $SCRIPTLOC daily 6";
CRON_W="0 0 * * 7 $SCRIPTLOC weekly 51";

# Help

help()
{
    echo ""
    echo "rsyncshot - compact snapshots on Linux using rsync and hard links."
    echo ""
    echo "rsyncshot must be run as root"
    echo ""
    echo "Usage: "
    echo "rsyncshot <name> <number of backups to retain>"
    echo "          setup (installs rsyncshot and cron jobs)"
    echo "          help  (prints this info)"
    echo "Notes:"
    echo "- install and log locations defined in script."

# Error

error()
{
    echo "ERROR: $0: $@" 1>&2;
    echo "See \"rsyncshot help\" for usage."
    exit 1;
}

# Setup

setup()
{
    # copy this file to directory on path
    cp -f $0 /usr/local/bin
    echo "$0 copied to /usr/local/bin"
    
    # make install home if it doesn't exist;
    if [ ! -d $INSTALLHOME ]; then
	mkdir -p $INSTALLHOME;
	"Created install home at $INSTALLHOME.";
    fi

    # create includes file and add default entries
    if [ -f $INCLUDES ]; then rm $INCLUDES; fi
    printf "/home /etc /usr/local/bin" >> $INCLUDES;
    echo "modify include file at $INCLUDES";

    # create excludes file and add default entries
    if [ -f $EXCLUDES ]; then rm $EXCLUDES; fi
    printf "*.pyc\n*.pyo\n*.class\n*.elc\n*.o\n*.tmp\n.cache*" >>  $EXCLUDES;
    echo "modify exclude file at $EXCLUDES";

    # write out current crontab, append default entries, and install
    crontab -l > crontemp;
    echo "$CRON_H >> $LOGHOME 2>&1" >> crontemp;
    echo "$CRON_D >> $LOGHOME 2>&1" >> crontemp;
    echo "$CRON_W >> $LOGHOME 2>&1">> crontemp;
    crontab crontemp;
    rm crontemp;
    echo "hourly, daily, and weekly cron jobs installed.";
}

# Check Help Option

if [ "$TYPE" = "HELP" ]; then help; exit; fi

# Ensure Root

if [ "$EUID" -ne 0 ]; then error "This script must be run as root."; fi

# Display How Script Was Started

echo "rsyncshot invoked on `date -u` with: $0 $1 $2";

# Parameter Validation

if ! [[ $1 =~ [a-zA-Z] ]]; then error "snapshot type not recognized."; fi
TYPE=$(tr '[a-z]' '[A-Z]' <<< $1);
if [ "$TYPE" = "SETUP" ]; then setup; exit; fi

# Validate Max Snapshots

if ! [[ $2 =~ [0-9] ]]; then error "max snapshots not a number."; fi
MAX=$(($2-1));

# Validate Include File (Source Directories) Exist

if [ ! -f "$INCLUDES" ]; then error "include file $INCLUDES not found."; fi
SOURCES=$(<$INCLUDES);
for SOURCE in $SOURCES
do
    if [ ! -d "$SOURCE" ]; then error "source $SOURCE not found"; fi
done

# Validate Exclude File (Exclusion Patterns) Exist

if [ ! -f "$EXCLUDES" ]; then error "Exclude file $EXCLUDES not found."; fi

# Sync Each Directory In Turn

for SOURCE in $SOURCES
do
    rsync -avh -i --times \
    	  --delete  --delete-excluded \
    	  --exclude-from=$EXCLUDES \
    	  --update $SOURCE $DESTINATION/latest ;
done

# Delete Max+1 Snapshot If Exists

if [ -d $DESTINATION/$TYPE.$MAX ]; then
    rm -rf $DESTINATION/$TYPE.$MAX;
fi

# Rotate Snapshots Descending

for (( start=$(($MAX)); start>=0; start--)); do
    end=$(($start+1));
	if [ -d $DESTINATION/$TYPE.$start ]; then
	    mv $DESTINATION/$TYPE.$start $DESTINATION/$TYPE.$end;
	fi
done

# Reset Directory Timestamp

touch $DESTINATION/latest

# Hard Link Only Copy to Destination

cp -al $DESTINATION/latest $DESTINATION/$TYPE.0;

# Make Directory Type Read-Only

chmod -w $DESTINATION/$TYPE.0

# Print Time and Exit

echo "rsyncshot completed `date -u` ";
exit 0;