| 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() |