105 # |
104 # |
106 |
105 |
107 class Backup(TerminableThread): |
106 class Backup(TerminableThread): |
108 |
107 |
109 def __decode_config(self, cfg): |
108 def __decode_config(self, cfg): |
110 loc0='backup target %d' % self.identifier |
109 loc0='Backup %d' % self.identifier |
111 |
110 |
112 self._name=config.check_string(cfg, 'name', 'Name', loc0) |
111 self.backup_name=config.check_string(cfg, 'name', 'Name', loc0) |
113 |
112 |
114 self.logger=logger.getChild(self._name) |
113 logger.debug("Configuring backup '%s'" % self.backup_name) |
115 |
114 |
116 self.loc='backup target "%s"' % self._name |
115 self.logger=logger.getChild(self.backup_name) |
|
116 |
|
117 loc="Backup '%s'" % self.backup_name |
117 |
118 |
118 reponame=config.check_string(cfg, 'repository', |
119 reponame=config.check_string(cfg, 'repository', |
119 'Target repository', self.loc) |
120 'Target repository', loc) |
120 |
121 |
121 self.repository=repository.get_controller(reponame) |
122 self.repository=repository.find_repository(reponame) |
|
123 if not self.repository: |
|
124 raise Exception("Repository '%s' not configured" % reponame) |
122 |
125 |
123 self.archive_prefix=config.check_string(cfg, 'archive_prefix', |
126 self.archive_prefix=config.check_string(cfg, 'archive_prefix', |
124 'Archive prefix', self.loc) |
127 'Archive prefix', loc) |
125 |
128 |
126 self.archive_template=config.check_string(cfg, 'archive_template', |
129 self.archive_template=config.check_string(cfg, 'archive_template', |
127 'Archive template', self.loc) |
130 'Archive template', loc) |
128 |
131 |
129 self.backup_interval=config.check_nonneg_int(cfg, 'backup_interval', |
132 self.backup_interval=config.check_nonneg_int(cfg, 'backup_interval', |
130 'Backup interval', self.loc, |
133 'Backup interval', loc, |
131 config.defaults['backup_interval']) |
134 config.defaults['backup_interval']) |
132 |
135 |
133 self.retry_interval=config.check_nonneg_int(cfg, 'retry_interval', |
136 self.retry_interval=config.check_nonneg_int(cfg, 'retry_interval', |
134 'Retry interval', self.loc, |
137 'Retry interval', loc, |
135 config.defaults['retry_interval']) |
138 config.defaults['retry_interval']) |
136 |
139 |
137 self.paths=config.check_nonempty_list_of_strings(cfg, 'paths', 'Paths', self.loc) |
140 self.paths=config.check_nonempty_list_of_strings(cfg, 'paths', 'Paths', loc) |
138 |
141 |
139 self.common_parameters=config.check_list_of_dicts(cfg, 'common_parameters', |
142 self.borg_parameters=config.BorgParameters.from_config(cfg, loc) |
140 'Borg parameters', self.loc, |
143 |
141 default=[]) |
|
142 |
|
143 self.create_parameters=config.check_list_of_dicts(cfg, 'create_parameters', |
|
144 'Create parameters', self.loc, |
|
145 default=[]) |
|
146 |
|
147 self.prune_parameters=config.check_list_of_dicts(cfg, 'prune_parameters', |
|
148 'Prune parameters', self.loc, |
|
149 default=[]) |
|
150 |
|
151 self.__keychain_account=config.check_string(cfg, 'keychain_account', |
|
152 'Keychain account', self.loc, |
|
153 default='') |
|
154 |
|
155 self.__passphrase=None |
|
156 |
|
157 if config.settings['extract_passphrases_at_startup']: |
|
158 try: |
|
159 self.extract_passphrase() |
|
160 except Exception: |
|
161 pass |
|
162 |
|
163 def extract_passphrase(self): |
|
164 acc=self.__keychain_account |
|
165 if not self.__passphrase: |
|
166 if acc and acc!='': |
|
167 self.logger.debug('Requesting passphrase') |
|
168 try: |
|
169 pw=keyring.get_password("borg-backup", acc) |
|
170 except Exception as err: |
|
171 self.logger.error('Failed to retrieve passphrase') |
|
172 raise err |
|
173 else: |
|
174 self.logger.debug('Received passphrase') |
|
175 self.__passphrase=pw |
|
176 else: |
|
177 self.__passphrase=None |
|
178 return self.__passphrase |
|
179 |
144 |
180 def __init__(self, identifier, cfg, scheduler): |
145 def __init__(self, identifier, cfg, scheduler): |
181 self.identifier=identifier |
146 self.identifier=identifier |
182 self.config=config |
|
183 self.__status_update_callback=None |
147 self.__status_update_callback=None |
184 self.scheduler=scheduler |
148 self.scheduler=scheduler |
185 self.logger=None # setup up __decode_config once backup name is known |
149 self.logger=None # setup up in __decode_config once backup name is known |
186 |
150 |
187 self.borg_instance=None |
151 self.borg_instance=None |
188 self.thread_log=None |
152 self.thread_log=None |
189 self.thread_res=None |
153 self.thread_res=None |
190 |
154 |
291 res=self.borg_instance.read_result() |
255 res=self.borg_instance.read_result() |
292 |
256 |
293 self.logger.debug('Borg result: %s' % str(res)) |
257 self.logger.debug('Borg result: %s' % str(res)) |
294 |
258 |
295 with self._cond: |
259 with self._cond: |
296 if res is None: |
260 if res is None and self.errors.ok(): |
297 self.logger.error('No result from borg despite no error in log') |
261 self.logger.error('No result from borg despite no error in log') |
298 if errors.ok(): |
262 self.errors=Errors.ERRORS |
299 self.errors=self.errors.combine(Errors.ERRORS) |
263 |
300 |
264 |
301 |
265 def __do_launch(self, op, archive_or_repository, |
302 def __do_launch(self, op, archive_or_repository, *args): |
266 common_params, op_params, paths=[]): |
303 passphrase=self.extract_passphrase() |
267 |
304 |
268 inst=BorgInstance(op.operation, archive_or_repository, |
305 inst=BorgInstance(op.operation, archive_or_repository, *args) |
269 common_params, op_params, paths) |
306 inst.launch(passphrase=passphrase) |
270 |
|
271 # Only the Repository object has access to the passphrase |
|
272 self.repository.launch_borg_instance(inst) |
307 |
273 |
308 self.logger.debug('Creating listener threads') |
274 self.logger.debug('Creating listener threads') |
309 |
275 |
310 t_log=Thread(target=self.__log_listener) |
276 t_log=Thread(target=self.__log_listener) |
311 t_log.daemon=True |
277 t_log.daemon=True |
326 self.__update_status() |
292 self.__update_status() |
327 |
293 |
328 t_log.start() |
294 t_log.start() |
329 t_res.start() |
295 t_res.start() |
330 |
296 |
|
297 |
331 def __launch(self, op): |
298 def __launch(self, op): |
332 self.logger.debug("Launching '%s'" % str(op.operation)) |
299 self.logger.debug("Launching '%s'" % str(op.operation)) |
333 |
300 |
|
301 params=(config.borg_parameters |
|
302 +self.repository.borg_parameters |
|
303 +self.borg_parameters) |
|
304 |
334 if op.operation==Operation.CREATE: |
305 if op.operation==Operation.CREATE: |
335 archive="%s::%s%s" % (self.repository.repository_name, |
306 archive="%s::%s%s" % (self.repository.location, |
336 self.archive_prefix, |
307 self.archive_prefix, |
337 self.archive_template) |
308 self.archive_template) |
338 |
309 |
339 self.__do_launch(op, archive, |
310 self.__do_launch(op, archive, params.common, |
340 self.common_parameters+self.create_parameters, |
311 params.create, self.paths) |
341 self.paths) |
|
342 elif op.operation==Operation.PRUNE: |
312 elif op.operation==Operation.PRUNE: |
343 self.__do_launch(op, self.repository.repository_name, |
313 self.__do_launch(op, self.repository.location, params.common, |
344 ([{'prefix': self.archive_prefix}] + |
314 [{'prefix': self.archive_prefix}] + params.create) |
345 self.common_parameters + |
315 |
346 self.prune_parameters)) |
|
347 else: |
316 else: |
348 raise NotImplementedError("Invalid operation '%s'" % str(op.operation)) |
317 raise NotImplementedError("Invalid operation '%s'" % str(op.operation)) |
349 |
318 |
350 # This must be called with self._cond held. |
319 # This must be called with self._cond held. |
351 def __launch_and_wait(self): |
320 def __launch_and_wait(self): |
464 self.logger.debug("Queuing") |
433 self.logger.debug("Queuing") |
465 self.state=State.QUEUED |
434 self.state=State.QUEUED |
466 self.__update_status() |
435 self.__update_status() |
467 res=self.repository.queue_action(self._cond, |
436 res=self.repository.queue_action(self._cond, |
468 action=self.__launch_and_wait, |
437 action=self.__launch_and_wait, |
469 name=self._name) |
438 name=self.backup_name) |
470 if not res and not self._terminate: |
439 if not res and not self._terminate: |
471 self.logger.debug("Queueing aborted") |
440 self.logger.debug("Queueing aborted") |
472 self.scheduled_operation=None |
441 self.scheduled_operation=None |
473 self.state=State.INACTIVE |
442 self.state=State.INACTIVE |
474 self.__update_status() |
443 self.__update_status() |