File: mrb

package info (click to toggle)
mrb 0.3%2Bnmu1
  • links: PTS
  • area: main
  • in suites: bullseye
  • size: 84 kB
  • sloc: makefile: 295; perl: 286
file content (392 lines) | stat: -rwxr-xr-x 15,850 bytes parent folder | download | duplicates (4)
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
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
#!/usr/bin/make -f
#
# mrb make/rsync backup system
#
# Copyright 2006 - 2014, Ron Lee.
# This file is distributed under the terms of the GNU GPL version 2.
#
# WARNING: Misconfigured and/or buggy backup systems can easily
#                      DESTROY YOUR PRECIOUS DATA
#          No warranty is, or can be, made against this happening
#          to you.  Your only protection is your own care and
#          vigilance.  This script is very simple, and should be
#          reasonably easy to audit for correctness according to
#          your particular needs.  You are strongly encouraged to
#          carefully test any new configuration, or release of it,
#          before deploying it on your only copy of critical data.
#
#          I may be a nice guy, and you may not even feel the need
#          to lock up your daughters or other valuables, but don't
#          trust me with your data.  Please.  It's too flimsy to
#          leave in the hands of someone whose attention is busy
#          enough worrying about their own.  Your help to make sure
#          nobody will ever have to read this warning 'in anger'
#          will be greatly appreciated.  So, on to the good bits ...
#
# -------------------------------------------------------------------
# Configurable fundamentals.

# A couple of simple axioms to kick things off.
override THIS_MAKEFILE := $(lastword $(MAKEFILE_LIST))

SHELL = /bin/bash

GLOBAL_CONFDIR = /etc
USER_CONFDIR   = ~/.mrb
USER_DEFAULTS  = $(USER_CONFDIR)/defaults

# Permit an expert user to reconfigure MRB_CONFDIR from an external file.
# There probably isn't much else you'd want to tweak there, but read on
# (carefully) if you really feel the need...
-include $(GLOBAL_CONFDIR)/default/mrb
-include $(USER_DEFAULTS)


# The directories to search for module definitions.
# A space separated list if multiple.  They are included in the order
# listed, but new modules will be added to only in the last one provided
# by default.
MRB_CONFDIR ?= $(GLOBAL_CONFDIR)/mrb $(USER_CONFDIR)

-include $(addsuffix /*.mrc,$(MRB_CONFDIR))


# Some basic environment for the operations to come.
USER ?= $(shell id -un)

# No need for --delete if we are linking into a barren dir.
RSYNC         = rsync
RSYNC_OPTIONS = --super -ahivS
DATESTAMP    := $(shell date +%Y%m%d-%H.%M)


# -------------------------------------------------------------------
# The rules.

# Default target explains how to press this button again properly.
default: help


# Convenience target for creating the skeleton of a new module.
new-%: NEW_FILE = $(lastword $(MRB_CONFDIR))/$*.mrc
new-%:

	@if [ -z "$(MRB_CONFDIR)" ]; then				\
	    echo; echo " Error: MRB_CONFDIR unset?  Aborting."; echo;	\
	    exit 1;							\
	fi
	@if [[ "$*" =~ [^[:alnum:]_] ]]; then				\
	    echo;							\
	    echo "  Error: Invalid Module name.  Names must consist of";\
	    echo "         only alphanumeric characters or underscore.";\
	    echo;							\
	    exit 1;							\
	fi
	@mkdir -p $(dir $(NEW_FILE))
	@if [ -e $(NEW_FILE) ]; then					\
	    echo; echo "  Module '$*' already exists.  Stopping."; echo;\
	    exit 1;							\
	fi

	@if [ ! -e $(USER_DEFAULTS) ]; then				\
	    echo;							\
	    echo "  Creating $(USER_DEFAULTS)";				\
	    echo;							\
	    echo "Edit this file to configure logging of transfers, or";\
	    echo "enable root 'sync' operations to create snapshots of";\
	    echo "other users' modules also.";				\
	    								\
	    echo "# This file configures mrb default settings for '$(USER)'" > $(USER_DEFAULTS);\
	    echo "# It was created on $(shell date) by $(THIS_MAKEFILE)" >> $(USER_DEFAULTS);\
	    echo >> $(USER_DEFAULTS);					\
	    echo "# An optional file path to log transfer details to." >> $(USER_DEFAULTS);\
	    echo "MRB_SNAPSHOT_LOG = ~/.mrb/snapshot.log" >> $(USER_DEFAULTS);\
	    echo >> $(USER_DEFAULTS);					\
	    echo "# A space separated list of users to include in a 'sync'." >> $(USER_DEFAULTS);\
	    echo "# This is typically only useful for the root user, as" >> $(USER_DEFAULTS);\
	    echo "# the identity of each user listed here is assumed when" >> $(USER_DEFAULTS);\
	    echo "# performing the sync of their modules." >> $(USER_DEFAULTS);\
	    echo "#MRB_SYNC_USERS = " >> $(USER_DEFAULTS);		\
	    echo >> $(USER_DEFAULTS);					\
	fi

	@echo
	@echo "  Creating skeleton $(NEW_FILE) ..."

	@echo "# This is the configuration file for the mrb snapshot module '$*'." > $(NEW_FILE)
	@echo "# It was created on $(shell date) for $(USER)" >> $(NEW_FILE)
	@echo "# by $(THIS_MAKEFILE)" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# A space separated list of the source files and directories to be" >> $(NEW_FILE)
	@echo "# included in snapshots of this module.  These may be remote urls." >> $(NEW_FILE)
	@echo "# Specifying this is mandatory." >> $(NEW_FILE)
	@echo "#$*_SRC = " >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# The (local) directory root where the snapshots should be stored." >> $(NEW_FILE)
	@echo "# Specifying this is mandatory." >> $(NEW_FILE)
	@echo "#$*_DEST = " >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# A pattern list for files under '$*_SRC' to explicitly include" >> $(NEW_FILE)
	@echo "# in the snapshot.  Specifying this is optional." >> $(NEW_FILE)
	@echo "# See rsync(1) for syntax details." >> $(NEW_FILE)
	@echo "#$*_INCLUDE = *.o/ *.d/ core/ *.a/ *.dll/ *.mo/ *.lo/ *.la/ *.so/ \\" >> $(NEW_FILE)
	@echo "#             *.init.d .*.s[a-w][a-z]/ .s[a-w][a-z]/" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# A pattern list for files under '$*_SRC' to explicitly exclude" >> $(NEW_FILE)
	@echo "# from the snapshot.  Specifying this is optional." >> $(NEW_FILE)
	@echo "# See rsync(1) for syntax details." >> $(NEW_FILE)
	@echo "#$*_EXCLUDE = *.o *.d core *.a *.dll *.gch *.mo *.lo *.la .libs/ \\" >> $(NEW_FILE)
	@echo "#             *.so *.so.[0-9] *.so.[0-9].[0-9] *.so.[0-9].[0-9].[0-9] \\" >> $(NEW_FILE)
	@echo "#             .*.s[a-w][a-z] .s[a-w][a-z]" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# An rsync filter rule to apply for the snapshot." >> $(NEW_FILE)
	@echo "# Specifying this is optional.  See rsync(1) for syntax details." >> $(NEW_FILE)
	@echo "#$*_FILTER = " >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# The file-name for an rsync per-directory filter to use if found." >> $(NEW_FILE)
	@echo "# Specifying this is optional.  See rsync(1) for syntax details" >> $(NEW_FILE)
	@echo "# of 'dir-merge' filters.  This is the name the file must match." >> $(NEW_FILE)
	@echo "# The default given here will scan all dirs from $*_SRC down." >> $(NEW_FILE)
	@echo "# Remove the leading '/' to search only the actual directory" >> $(NEW_FILE)
	@echo "# that is being transferred." >> $(NEW_FILE)
	@echo "#$*_FILTER_FILE = /.mrb-rsync-filter" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# Optional additional rsync options to pass verbatim." >> $(NEW_FILE)
	@echo "#$*_RSYNC_OPTIONS = --relative --prune-empty-dirs" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# An optional shell command to execute before making the snapshot." >> $(NEW_FILE)
	@echo "# If the command does not return a successful exit status, then the" >> $(NEW_FILE)
	@echo "# snapshot creation will be aborted before it begins." >> $(NEW_FILE)
	@echo "# It may be used to mount $*_DEST on removable media or similar." >> $(NEW_FILE)
	@echo "#$*_PRECOMMAND = mount /mnt/backup" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# An optional shell command to execute after making the snapshot." >> $(NEW_FILE)
	@echo "# It will not be called if the snaphot creation failed at an earlier" >> $(NEW_FILE)
	@echo "# stage.  It's exit status will be passed to the user, but nothing" >> $(NEW_FILE)
	@echo "# remains to be done with it at this level." >> $(NEW_FILE)
	@echo "# It may be used, for example, to unmount removable media again." >> $(NEW_FILE)
	@echo "#$*_POSTCOMMAND = umount /mnt/backup" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# An optional user name to check before performing a snapshot." >> $(NEW_FILE)
	@echo "# This can be used to ensure you have the correct permisson to" >> $(NEW_FILE)
	@echo "# access the files being mirrored before you get too far." >> $(NEW_FILE)
	@echo "$*_USER = $(USER)" >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo >> $(NEW_FILE)
	@echo "# Do not remove or edit this entry, it is used internally by mrb." >> $(NEW_FILE)
	@echo "$*_CONFIG = \$$(lastword \$$(MAKEFILE_LIST))" >> $(NEW_FILE)

	@echo
	@echo "Please edit this file now to define the details of the"
	@echo "snapshot which you would like it to create."
	@echo


# Some basic checks shared by multiple operations.
define BASIC_SANITY
	if [ -z "$($*_CONFIG)" ]; then					\
	    echo;							\
	    echo "  No module '$*'.  Aborting backup run.";		\
	    echo "  You may create it with: \`$(THIS_MAKEFILE) new-$*\`";\
	    echo;							\
	    exit 1;							\
	fi;								\
	if [ -z "$($*_SRC)" ]; then					\
	    echo;							\
	    echo "  Module $*_SRC not defined.  Aborting backup run.";	\
	    echo "  Edit $($*_CONFIG) first, then try again.";		\
	    echo;							\
	    exit 1;							\
	fi;								\
	if [ -z "$($*_DEST)" ]; then					\
	    echo;							\
	    echo "  Module $*_DEST not defined.  Aborting backup run.";	\
	    echo "  Edit $($*_CONFIG) first, then try again.";		\
	    echo;							\
	    exit 1;							\
	fi
	if [ -n "$($*_USER)" ] && [ "$($*_USER)" != "$(USER)" ]; then	\
	    echo;							\
	    echo "  Module requires user '$($*_USER)'.  Aborting backup run.";\
	    echo "  Edit $($*_CONFIG), or run as the required user.";	\
	    echo;							\
	    exit 1;							\
	fi
endef


# Convenience target for initialising a new module destination dir.
# The snapshot target cowardly refuses to create new destinations
# as a basic sanity trap.
# Note: The pre- and post-commands are not run for this target.
dest-%:

	@$(BASIC_SANITY)
	@echo;
	@echo "  Creating '$($*_DEST)' dir"
	@echo;
	@mkdir -p $($*_DEST)


# Syntax highlighting hack for the quote removal operation.
DOUBLE_QUOTE := "
#"

# Helper target for issuing (more or less) arbitrary post-commands.
do_postcommand-%:

	@[ x"$(subst $(DOUBLE_QUOTE),_,$($*_POSTCOMMAND))" = x ] ||	\
	    echo "Executing post-commands:"
	$($*_POSTCOMMAND)


# The real worker target, creates a snapshot of some module.
do_snap-%: LAST_SNAP = $(notdir $(lastword $(sort $(wildcard $($*_DEST)/$*-*.mrb))))
do_snap-%: THIS_SNAP = $($*_DEST)/$*-$(DATESTAMP).mrb
do_snap-%: RSYNC_OPTIONS += $(addprefix --link-dest=,$(addprefix ../,$(LAST_SNAP)))
do_snap-%: RSYNC_OPTIONS += $(addprefix --include=',$(addsuffix ',$($*_INCLUDE)))
do_snap-%: RSYNC_OPTIONS += $(addprefix --exclude=',$(addsuffix ',$($*_EXCLUDE)))
do_snap-%: RSYNC_OPTIONS += $(addprefix --filter=',$(addsuffix ',$($*_FILTER)))
do_snap-%: RSYNC_OPTIONS += $(addprefix --filter=': ,$(addsuffix ',$($*_FILTER_FILE)))
do_snap-%: RSYNC_OPTIONS += $($*_RSYNC_OPTIONS)
do_snap-%: LOG_OUTPUT = $(addprefix 2>&1 | tee -a ,$(MRB_SNAPSHOT_LOG))
do_snap-%:

#	Begin with a little more sanity checking

	@for s in $($*_SRC); do						\
	    if [ ! -r "$$s" ]; then					\
		if [[ $$s == *:* ]]; then continue; fi;			\
		echo;							\
		echo "  Source '$$s' does not exist or is not readable.";\
	        echo "  Aborting backup run.";				\
		echo;							\
	        $(THIS_MAKEFILE) --no-print-directory do_postcommand-$*;\
		exit 1;							\
	    fi;								\
	done
	@if [ ! -d $($*_DEST) ]; then					\
	    echo;							\
	    echo "  Destination dir '$($*_DEST)' does not exist.";	\
	    echo "  Aborting backup run.";				\
	    echo "  If this destination is correct, but new, you may create it now";\
	    echo "  with the command: \`$(THIS_MAKEFILE) dest-$*\`";	\
	    echo;							\
	    $(THIS_MAKEFILE) --no-print-directory do_postcommand-$*;	\
	    exit 1;							\
	fi

#	Tell 'em what we're going to do.

	@echo
	@echo " Making $*-$(DATESTAMP) snapshot"
	@echo "  from: $($*_SRC)"
	@echo "  to  : $($*_DEST)"
	@[ -z "$($*_INCLUDE)" ] || echo "  include : $($*_INCLUDE)"
	@[ -z "$($*_EXCLUDE)" ] || echo "  exclude : $($*_EXCLUDE)"
	@[ -z "$($*_FILTER)" ]  || echo "  filter  : $($*_FILTER)"
	@if [ -z "$(LAST_SNAP)" ]; then					\
	    echo " Performing full copy.";				\
	else								\
	    echo " Incremental update based on '$(LAST_SNAP)'";		\
	fi
	@echo

#	Do it.

	@mkdir $(THIS_SNAP)
	@(echo; echo "    ==== $(THIS_SNAP) ===="; echo)          >> $(MRB_SNAPSHOT_LOG)
	@echo "==> $(RSYNC) $(RSYNC_OPTIONS) $($*_SRC) $(THIS_SNAP)" $(LOG_OUTPUT)
	@$(RSYNC) $(RSYNC_OPTIONS) $($*_SRC) $(THIS_SNAP)            $(LOG_OUTPUT)
	@echo                                                        $(LOG_OUTPUT)

	@[ x"$(MRB_SNAPSHOT_LOG)" = x ] || echo "Logged transfer to '$(MRB_SNAPSHOT_LOG)'"


# Main user entry-point for creating new snapshots.
# We do any PRECOMMAND's here, before forwarding to the main worker target,
# as they may be needed to prepare the source and/or dest mount points
# before the target specific variables are assigned their final values.
snap-%:

	@$(BASIC_SANITY)

	@[ -z "$(subst $(DOUBLE_QUOTE),_,$($*_PRECOMMAND))" ] || echo "Executing pre-commands:"
	$($*_PRECOMMAND)

	@$(THIS_MAKEFILE) --no-print-directory do_$@
	@$(THIS_MAKEFILE) --no-print-directory do_postcommand-$*

#	Tell 'em its done.

	@echo; echo "All done.  You may resume normal breathing patterns now."


# Helper target for multiple user sync.
user_sync-%:

	@echo; echo "Begin sync for '$*'"
	@su - $* -c '$(THIS_MAKEFILE) --no-print-directory sync'


# Convenience target to create a snapshot of all defined modules.
# If called by an ordinary user, it will find all the modules
# available to that user.  If MRB_SYNC_USERS contains a list of
# users, it will try to sync for them as well.  Typically this is
# only useful for root, as we must assume their identity to
# reliably perform that task.
sync: $(addprefix snap-,$(notdir $(basename $(foreach d,		\
                        $(MRB_CONFDIR),$(wildcard $(d)/*.mrc)))))	\
      $(addprefix user_sync-,$(MRB_SYNC_USERS))

	@echo "Full sync for '$(USER)' done"


# Convenience target to check the required config files are available.
info:

	@echo
	@echo "$(THIS_MAKEFILE) is reading the following configuration files:"
	@for f in $(filter-out $(THIS_MAKEFILE),$(MAKEFILE_LIST)); do	\
	    echo "  $$f";						\
	 done
	@echo "as user '$(USER)'."
	@echo


# If I have to explain this one, then I guess you are just reading this
# 'for the articles' -- but I hope you'll have enjoyed it anyway...
help:

	@echo
	@echo "Usage: $(THIS_MAKEFILE) <command>"
	@echo
	@echo "mrb is a simple, on demand, backup system for taking a snapshot"
	@echo "of a set of directories.  The following commands are recognised"
	@echo "(where 'MODULE' is the name of one of your snapshot definitions):"
	@echo
	@echo " new-MODULE  - Create a skeleton definition for a new snapshot 'MODULE'."
	@echo " dest-MODULE - Create the destination dir for 'MODULE'."
	@echo "               This directory must exist to create a snapshot."
	@echo
	@echo " snap-MODULE - Create a snapshot of 'MODULE'."
	@echo " sync        - Create snapshots of all defined modules."
	@echo "               If run as root this may be configured to include"
	@echo "               the modules of other users too (see MRB_SYNC_USERS"
	@echo "               in ~/.mrb/defaults)."
	@echo
	@echo " info        - Report on the available config and module files."
	@echo " help        - Display this text again."
	@echo


.PHONY: default info help

# TODO:
#       How to link back to the last N snapshots if useful.. ?
#       (note you can presently override the value of LAST_SNAP, or
#       add additional --link-dest commands via module_RSYNC_OPTIONS)
#       Support for per-user precommand and postcommands instead
#       of executing the same ones repeatedly for each snapshot.
#       Report on file additions/removals since the last incremental.