43 general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.WARNING, help="Less output") |
43 general.add_option('-q', '--quiet', dest='loglevel', action='store_const', const=logging.WARNING, help="Less output") |
44 general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output") |
44 general.add_option('-v', '--verbose', dest='loglevel', action='store_const', const=logging.INFO, help="More output") |
45 general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output") |
45 general.add_option('-D', '--debug', dest='loglevel', action='store_const', const=logging.DEBUG, help="Even more output") |
46 |
46 |
47 parser.add_option_group(general) |
47 parser.add_option_group(general) |
|
48 |
|
49 # rsync |
|
50 rsync = optparse.OptionGroup(parser, "rsync Options") |
|
51 |
|
52 rsync.add_option('--exclude-from', metavar='FILE', |
|
53 help="Read exclude rules from given file") |
|
54 |
|
55 rsync.add_option('--include-from', metavar='FILE', |
|
56 help="Read include rules from given file") |
|
57 |
|
58 parser.add_option_group(rsync) |
48 |
59 |
49 # |
60 # |
50 parser.add_option('-s', '--source', metavar='RSYNC-PATH', |
61 parser.add_option('-s', '--source', metavar='RSYNC-PATH', |
51 help="Backup source in rsync-syntax") |
62 help="Backup source in rsync-syntax") |
52 |
63 |
119 level = options.loglevel, |
130 level = options.loglevel, |
120 ) |
131 ) |
121 |
132 |
122 if options.clean : |
133 if options.clean : |
123 options.clean_intervals = options.clean_snapshots = options.clean |
134 options.clean_intervals = options.clean_snapshots = options.clean |
|
135 |
|
136 if options.include_from : |
|
137 options.rsync_options['include-from'] = options.include_from |
|
138 |
|
139 if options.exclude_from : |
|
140 options.rsync_options['exclude-from'] = options.exclude_from |
124 |
141 |
125 return options, args |
142 return options, args |
126 |
143 |
127 def run_snapshot (options) : |
144 def run_snapshot (options) : |
128 """ |
145 """ |
147 |
164 |
148 # build rsync options |
165 # build rsync options |
149 opts = dict(options.rsync_options) |
166 opts = dict(options.rsync_options) |
150 |
167 |
151 if os.path.exists(options.current_path) : |
168 if os.path.exists(options.current_path) : |
152 # use as link-dest base; hardlinks unchanged files |
169 # real path to target |
153 opts['link-dest'] = options.current_path |
170 target = os.readlink(options.current_path) |
|
171 target_path = os.path.join(os.path.dirname(options.current_path), target) |
|
172 target_abs = os.path.abspath(target_path) |
|
173 |
|
174 log.info("Using current -> %s as base", target_path) |
|
175 |
|
176 # use as link-dest base; hardlinks unchanged files; target directory must be empty |
|
177 # rsync links absolute paths.. |
|
178 opts['link-dest'] = target_abs |
154 |
179 |
155 # go |
180 # go |
156 log.debug("rsync %s -> %s", options.source, temp_path) |
181 log.debug("rsync %s -> %s", options.source, temp_path) |
157 rsync.rsync(options.source, temp_path, **opts) |
182 rsync.rsync(options.source, temp_path, **opts) |
158 |
183 |
191 |
216 |
192 # already there? |
217 # already there? |
193 if os.path.exists(path) : |
218 if os.path.exists(path) : |
194 target = os.readlink(path) |
219 target = os.readlink(path) |
195 |
220 |
196 log.info("Found existing %s: %s -> %s", interval, name, target) |
221 log.info("%s: Found existing: %s -> %s", interval, name, target) |
197 |
222 |
198 else : |
223 else : |
199 # update |
224 # update |
200 target = os.path.join('..', 'snapshots', snapshot_name) |
225 target = os.path.join('..', 'snapshots', snapshot_name) |
201 |
226 |
202 log.info("Updating %s: %s -> %s", interval, name, target) |
227 log.info("%s: Updating: %s -> %s", interval, name, target) |
203 log.debug("%s -> %s", path, target) |
228 log.debug("%s -> %s", path, target) |
204 |
229 |
205 os.symlink(target, path) |
230 os.symlink(target, path) |
206 |
231 |
207 |
232 |
347 """ |
372 """ |
348 |
373 |
349 # timestamp for run |
374 # timestamp for run |
350 options.now = datetime.datetime.now() |
375 options.now = datetime.datetime.now() |
351 |
376 |
|
377 # clean intervals? |
|
378 if options.clean_intervals: |
|
379 for interval in options.intervals : |
|
380 log.info("Cleaning interval: %s...", interval) |
|
381 |
|
382 clean_interval(options, interval) |
|
383 |
|
384 # clean snapshots? |
|
385 if options.clean_snapshots : |
|
386 log.info("Cleaning snapshots...") |
|
387 |
|
388 clean_snapshots(options) |
|
389 |
352 # snapshot from source? |
390 # snapshot from source? |
353 if options.source : |
391 if options.source : |
354 # base snapshot (symlink) |
392 # base snapshot (symlink) |
355 options.current_path = os.path.join(options.destination, 'current') |
393 options.current_path = os.path.join(options.destination, 'current') |
356 |
394 |
372 if not options.intervals : |
410 if not options.intervals : |
373 log.info("No --intervals given; not running any") |
411 log.info("No --intervals given; not running any") |
374 |
412 |
375 else : |
413 else : |
376 # maintain intervals |
414 # maintain intervals |
377 log.info("Running intervals: %s", options.intervals) |
415 log.info("Updating %d intervals...", len(options.intervals)) |
378 |
416 |
379 for interval in options.intervals : |
417 for interval in options.intervals : |
380 log.debug("%s", interval) |
418 log.debug("%s", interval) |
381 |
419 |
|
420 log.info("Updating interval: %s", interval) |
|
421 |
382 # update |
422 # update |
383 update_interval(options, snapshot_name, interval) |
423 update_interval(options, snapshot_name, interval) |
384 |
|
385 # clean intervals? |
|
386 if options.clean_intervals: |
|
387 for interval in options.intervals : |
|
388 log.info("Cleaning interval: %s...", interval) |
|
389 |
|
390 clean_interval(options, interval) |
|
391 |
|
392 # clean snapshots? |
|
393 if options.clean_snapshots : |
|
394 log.info("Cleaning snapshots...") |
|
395 |
|
396 clean_snapshots(options) |
|
397 |
424 |
398 # ok |
425 # ok |
399 return 1 |
426 return 1 |
400 |
427 |
401 def main (argv) : |
428 def main (argv) : |