-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathrsync-fs
executable file
·298 lines (280 loc) · 6.82 KB
/
rsync-fs
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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
#!/usr/bin/env bash
#
# Synopsis:
# rsync an entire file system to <sunk-root>/host/$(hostname)/root/<mount>
# Description:
# Create a full rsunk snapshot of a single file system to a local
#
# <sunk-root>/host/$(hostname)/root/<mount>
#
# Typically the file system <mount> directory is / or /var and the
# <sunk> directory is /backup/sunk or /sunk.
#
# The sunk root and mount directories MUST be both full paths and point
# to existing directories. However, the final, target sunk directory will
# be created, if needed. The options --mount-point and --sunk-root
# are required.
#
# The rsync arguments are:
#
# --numeric-ids
# --filter=<mount>/.rsync-filter
# '--exclude=<sunk-root>/*'
# --archive
# --delete
# --delete-excluded
# --human-readable
# --itemize-changes
# --one-file-system
# --exclude /backup/sunk/$(hostname)/root/<fs>
#
# When an error occurs, the --fault <path> option indicates the error
# message will be appended to the <path> file, allowing for easy
# snmp monitoring of process failures.
#
# Files that disappear during the rsync will generate a fault, since
# rsync exits with code 24. To ignore such rsync errors (and not fault)
# add the command line option --ignore-24.
# Usage:
# LOG=/var/local/log/rsync-fs-root.log
# FAULT=/var/run/rsync-fs-var.fault
#
# rsync-fs --sunk-root /sunk --mount-point / >$LOG
# #
# # catalina+ on mac forbids writes to root!
# #
# rsync-fs --filter /tmp/rsync-filter --sunk-root /sunk --mount-point /
# rsync-fs --mount-point /var --sunk-root /backup/sunk
# rsync-fs --mount-point /var --fault $FAULT --sunk-root /sunk
# rsync-fs --mount-point /var --ignore-24 --sunk-root /sunk
#
# # Or for daily snapshots with faults in /var/run
# LOG=/var/log/rsync-fs
# FAULT=/var/run/rsync-root.fault
#
# mkdir -p $LOG || exit
# rsync-fs --mount-point /var --sunk-root /backup/sunk --fault $FAULT \
# >$LOG/rsync-var-`%a`.log
# Exit Status:
# 0 rsync exited 0
# 127 failure (not rsync error)
# See:
# https://github.com/jmscott/work/blob/master/rsync-fs
# Note:
# Verify that the source and target file system are not the same.
# rsync may already do this.
#
# Add rync option "--checksum"
#
# Robust tests on multiple level dirs like /var/lib have not been
# exercised.
#
# Think about verifying the source is a file system, in a portable
# manner.
#
# Need to add notify.
#
START_EPOCH=$(date +%s)
PROG=$(basename $0)
HOST=$(hostname -f)
MOUNT_POINT=
SUNK_ROOT=
FAULT=
SUNK_TGT=
IGNORE_24=no
USER=${USER:=$LOGNAME}
#
# Burp the comments section above and exit.
#
help()
{
#
# Sorry about using perl for the absolute path derivation.
# The Mac OSX 10.7/bds readlink is different than
# the gnu readlink.
#
ABSPATH=$(perl -MCwd=realpath -e "print realpath '$0'")
awk '
!/^ *#/ {
exit
}
!/^# *!/
' $ABSPATH | sed 's/^# *//'
exit 0
}
log()
{
echo "$(date +'%Y-%m-%d %H:%M:%S'): $PROG#$$: $@"
}
warn()
{
log "WARN: $@"
}
die()
{
#
# Fire a fault by appending error message to file $FAULT.
#
MSG="ERROR: $@"
#
# Caller requested that we append error to fault file.
#
if [ -n "$FAULT" ]; then
log "appending error to fault file: $FAULT"
log $MSG >>$FAULT ||
log "ERROR: append to fault file failed: $FAULT" >&2
fi
log $MSG >&2
exit 127
}
leave()
{
log "good bye, cruel world: $(elapsed-english $START_EPOCH) elapsed"
}
test "$1" = '--help' && help
test $# -lt 4 && die 'wrong number of arguments'
case "$USER" in
'')
die 'environment vars not defined: LOGNAME and USER'
;;
root)
;;
*)
die "user is not root: $USER"
;;
esac
while [ "$1" ]; do
ARG=$1
shift
case $ARG in
--help)
help
;;
--inplace)
test -z "$INPLACE" || die 'option --inplace set again'
INPACE='--inplace'
;;
--dry-run)
test -z "$DRY_RUN" || die 'option --dry-run set again'
DRY_RUN='--dry-run'
;;
--mount-point)
test -z "$MOUNT_POINT" || die 'option --mount-point redefined'
MOUNT_POINT=$1; shift
case $MOUNT_POINT in
'')
die 'option --mount-point: missing path'
;;
/*)
test -d $MOUNT_POINT ||
die "option --mount-point:" \
"not a directory: $MOUNT_POINT"
;;
*)
die 'mount point directory must start with /'
;;
esac
;;
--sunk-root)
test -z "$SUNK_ROOT" || die 'option --sunk-root redefined'
SUNK_ROOT="$1"; shift
case $SUNK_ROOT in
'')
die 'option --mount-point: missing path'
;;
/*)
test -d $SUNK_ROOT ||
die "option --sunk-root:" \
"not a directory: $SUNK_ROOT"
;;
*)
die 'sunk root directory must start with /'
;;
esac
;;
--fault)
test -z "$FAULT" || die 'option --fault redefined'
FAULT="$1"; shift
test -n "$FAULT" || die 'option --fault: missing file path'
if [ -e "$FAULT" ]; then
warn "fault file exists: $FAULT"
warn "contents fault file: $(cat $FAULT)"
fi
;;
--filter)
test -z "$FILTER" || die 'option --filter given more than once'
FILTER="$1"; shift
;;
--ignore-24)
IGNORE_24=yes
;;
--*)
die "unknown command line option: $ARG"
;;
*)
die "unknown command line argument: $ARG"
;;
esac
done
test -z "$MOUNT_POINT" && die 'missing required option: --mount-point'
test -z "$SUNK_ROOT" && die 'missing required option: --sunk-root'
trap leave INT QUIT TERM EXIT
log 'hello, world'
if [ -z "$FAULT" ]; then
log 'no fault file given (ok): see option --fault'
else
log "fault file: $FAULT"
fi
log "hostname: $HOST"
log "PATH: $PATH"
log "rsync version: $(rsync --version | head -1)"
log "sunk root: $SUNK_ROOT"
log "source file system mount directory: $MOUNT_POINT"
log "ignore rsync error 24 (files vanished): $IGNORE_24"
#
# Build the rsync-filter path and target sunk directory path from the
# hostname, sunk root path and mount point.
#
case $MOUNT_POINT in
/)
SUNK_TGT=$SUNK_ROOT/host/$HOST/root
test -n "$FILTER" || FILTER='dir-merge /.rsync-filter'
;;
/*)
SUNK_TGT=$(dirname $SUNK_ROOT/host/$HOST/root$MOUNT_POINT)
test -n "$FILTER" || FILTER="dir-merge $MOUNT_POINT/.rsync-filter"
;;
*)
die "first char not / in mount directory: $MOUNT_POINT"
;;
esac
log "sunk target: $SUNK_TGT"
if [ ! -d $SUNK_TGT ]; then
log "creating sunk target directory: $SUNK_TGT"
mkdir -p $SUNK_TGT || die "mkdir sunk target failed: exit status=$?"
fi
log "rsync filter: $FILTER"
log 'starting rsync ...'
time rsync $INPLACE $DRY_RUN \
--numeric-ids \
--filter="$FILTER" \
"--exclude=$SUNK_ROOT/*" \
--archive \
--delete \
--delete-excluded \
--human-readable \
--itemize-changes \
--one-file-system \
--exclude $SUNK_TGT \
$MOUNT_POINT $SUNK_TGT
STATUS=$?
log "rsync exit status: $STATUS"
if [ $STATUS -ne 0 ]; then
if [ $STATUS -eq 24 -a $IGNORE_24 = yes ]; then
warn 'rsync exit of 24 and --ignore-24 requested'
warn 'ignoring rsync error code'
else
die "rsync failed: exit status=$STATUS"
fi
fi
exit 0