| 1 #!/usr/local/bin/python3 |
|
| 2 # |
|
| 3 # Borgend configuration loader |
|
| 4 # |
|
| 5 |
|
| 6 import yaml |
|
| 7 import io |
|
| 8 import os |
|
| 9 import string |
|
| 10 import logging |
|
| 11 import platform |
|
| 12 from functools import reduce |
|
| 13 import loggers |
|
| 14 import branding |
|
| 15 import locations |
|
| 16 |
|
| 17 logger=loggers.get(__name__) |
|
| 18 |
|
| 19 # |
|
| 20 # Defaults |
|
| 21 # |
|
| 22 |
|
| 23 defaults={ |
|
| 24 # borg |
|
| 25 # Default: backup every 6 hours (21600 seconds) |
|
| 26 'backup_interval': 21600, |
|
| 27 # Default: retry every 15 minutes if unable to connect / unfinished backup |
|
| 28 'retry_interval': 900, |
|
| 29 # Extract passphrases at startup or on demand? |
|
| 30 'extract_passphrases_at_startup': True, |
|
| 31 # Do not insert a quit menu entry (useful for installing on computers of |
|
| 32 # inexperienced users) |
|
| 33 'no_quit_menu_entry': False, |
|
| 34 # Borg settings |
|
| 35 'borg': { |
|
| 36 'executable': 'borg', |
|
| 37 'common_parameters': [], |
|
| 38 'create_parameters': [], |
|
| 39 'prune_parameters': [], |
|
| 40 } |
|
| 41 } |
|
| 42 |
|
| 43 # |
|
| 44 # Type checking etc. |
|
| 45 # |
|
| 46 |
|
| 47 def error(x): |
|
| 48 raise AssertionError(x) |
|
| 49 |
|
| 50 def check_field(cfg, field, descr, loc, default, check): |
|
| 51 if field in cfg: |
|
| 52 tmp=cfg[field] |
|
| 53 if not check(tmp): |
|
| 54 error("%s is of invalid type for %s" % (field, loc)) |
|
| 55 return tmp |
|
| 56 else: |
|
| 57 if default is not None: |
|
| 58 return default |
|
| 59 else: |
|
| 60 error("%s is not configured for %s" % (field, loc)) |
|
| 61 |
|
| 62 def check_bool(cfg, field, descr, loc, default=None): |
|
| 63 return check_field(cfg, field, descr, loc, default, |
|
| 64 lambda x: isinstance(x, bool)) |
|
| 65 |
|
| 66 def check_string(cfg, field, descr, loc, default=None): |
|
| 67 return check_field(cfg, field, descr, loc, default, |
|
| 68 lambda x: isinstance(x, str)) |
|
| 69 |
|
| 70 def check_dict(cfg, field, descr, loc, default=None): |
|
| 71 return check_field(cfg, field, descr, loc, default, |
|
| 72 lambda x: isinstance(x, dict)) |
|
| 73 |
|
| 74 def check_list(cfg, field, descr, loc, default=None): |
|
| 75 return check_field(cfg, field, descr, loc, default, |
|
| 76 lambda x: isinstance(x, list)) |
|
| 77 |
|
| 78 def is_list_of(x, chk): |
|
| 79 if x is None: |
|
| 80 return True |
|
| 81 elif isinstance(x, list): |
|
| 82 return reduce(lambda y, z: y and chk(z), x, True) |
|
| 83 else: |
|
| 84 return False |
|
| 85 |
|
| 86 def check_list_of_dicts(cfg, field, descr, loc, default=None): |
|
| 87 return check_field(cfg, field, descr, loc, default, |
|
| 88 lambda x: is_list_of(x, lambda z: isinstance(z, dict))) |
|
| 89 |
|
| 90 def check_list_of_strings(cfg, field, descr, loc, default=None): |
|
| 91 return check_field(cfg, field, descr, loc, default, |
|
| 92 lambda x: is_list_of(x, lambda z: isinstance(z, str))) |
|
| 93 |
|
| 94 def check_nonempty_list_of_strings(cfg, field, descr, loc): |
|
| 95 return check_list_of_strings(cfg, field, descr, loc) and cfg[field] |
|
| 96 |
|
| 97 |
|
| 98 def check_nonneg_int(cfg, field, descr, loc, default=None): |
|
| 99 return check_field(cfg, field, descr, loc, default, |
|
| 100 lambda x: isinstance(x, int) and x>=0) |
|
| 101 |
|
| 102 |
|
| 103 # |
|
| 104 # Borg command line parameter configuration helper routines and classes |
|
| 105 # |
|
| 106 |
|
| 107 class BorgParameters: |
|
| 108 def __init__(self, common, create, prune): |
|
| 109 self.common=common or [] |
|
| 110 self.create=create or [] |
|
| 111 self.prune=prune or [] |
|
| 112 |
|
| 113 def from_config(cfg, loc): |
|
| 114 common=check_list_of_dicts(cfg, 'common_parameters', |
|
| 115 'Borg parameters', loc, default=[]) |
|
| 116 |
|
| 117 create=check_list_of_dicts(cfg, 'create_parameters', |
|
| 118 'Create parameters', loc, default=[]) |
|
| 119 |
|
| 120 prune=check_list_of_dicts(cfg, 'prune_parameters', |
|
| 121 'Prune parameters', loc, default=[]) |
|
| 122 |
|
| 123 return BorgParameters(common, create, prune) |
|
| 124 |
|
| 125 def __add__(self, other): |
|
| 126 common=self.common+other.common |
|
| 127 create=self.create+other.create |
|
| 128 prune=self.prune+other.prune |
|
| 129 return BorgParameters(common, create, prune) |
|
| 130 |
|
| 131 # |
|
| 132 # Load config on module load |
|
| 133 # |
|
| 134 |
|
| 135 def expand_env(cfg, env): |
|
| 136 if isinstance(cfg, dict): |
|
| 137 out={key: expand_env(val, env) for key, val in cfg.items()} |
|
| 138 elif isinstance(cfg, list): |
|
| 139 out=[expand_env(val, env) for val in cfg] |
|
| 140 elif isinstance(cfg, str): |
|
| 141 out=string.Template(cfg).substitute(os.environ) |
|
| 142 else: |
|
| 143 out=cfg |
|
| 144 |
|
| 145 return out |
|
| 146 |
|
| 147 if not (os.path.exists(locations.cfgfile) and os.path.isfile(locations.cfgfile)): |
|
| 148 raise SystemExit("Configuration file required: %s" % locations.cfgfile) |
|
| 149 |
|
| 150 logger.info("Reading configuration %s missing" % locations.cfgfile) |
|
| 151 |
|
| 152 with io.open(locations.cfgfile, 'r') as file: |
|
| 153 settings=expand_env(yaml.load(file), os.environ); |
|
| 154 |
|
| 155 # |
|
| 156 # Verify basic settings |
|
| 157 # |
|
| 158 |
|
| 159 def check_and_set(cfg, field, loc, defa, fn): |
|
| 160 cfg[field]=fn(cfg, field, field, loc, defa[field]) |
|
| 161 |
|
| 162 check_and_set(settings, 'backup_interval', 'top-level', defaults, check_nonneg_int) |
|
| 163 check_and_set(settings, 'retry_interval', 'top-level', defaults, check_nonneg_int) |
|
| 164 check_and_set(settings, 'extract_passphrases_at_startup', 'top-level', defaults, check_nonneg_int) |
|
| 165 check_and_set(settings, 'no_quit_menu_entry', 'top-level', defaults, check_bool) |
|
| 166 check_and_set(settings, 'borg', 'top-level', defaults, check_dict) |
|
| 167 # Check parameters within 'borg' |
|
| 168 if True: |
|
| 169 check_and_set(settings['borg'], 'executable', 'borg', |
|
| 170 defaults['borg'], check_string) |
|
| 171 |
|
| 172 borg_parameters=BorgParameters.from_config(settings['borg'], "top-level") |
|
| 173 |
|