| 5 import yaml |
5 import yaml |
| 6 import io |
6 import io |
| 7 import os |
7 import os |
| 8 import xdg |
8 import xdg |
| 9 import string |
9 import string |
| |
10 from functools import reduce |
| 10 |
11 |
| |
12 # |
| |
13 # Defaults |
| |
14 # |
| |
15 |
| |
16 defaults={ |
| |
17 # Default: backup every 6 hours (21600 seconds) |
| |
18 'backup_interval': 21600, |
| |
19 # Default: retry every 15 minutes if unable to connect / unfinished backup |
| |
20 'retry_interval': 900, |
| |
21 # borg |
| |
22 'borg': { |
| |
23 'executable': 'borg', |
| |
24 'common_parameters': [], |
| |
25 'create_parameters': [], |
| |
26 } |
| |
27 } |
| |
28 |
| |
29 |
| |
30 # |
| |
31 # Type checking etc. |
| |
32 # |
| |
33 |
| |
34 def error(x): |
| |
35 raise AssertionError(x) |
| |
36 |
| |
37 def check_string(cfg, field, descr, loc, default=None): |
| |
38 return check_field(cfg, field, descr, loc, default, |
| |
39 lambda x: isinstance(x, str)) |
| |
40 |
| |
41 def check_dict(cfg, field, descr, loc, default=None): |
| |
42 return check_field(cfg, field, descr, loc, default, |
| |
43 lambda x: isinstance(x, dict)) |
| |
44 |
| |
45 def check_list(cfg, field, descr, loc, default=None): |
| |
46 return check_field(cfg, field, descr, loc, default, |
| |
47 lambda x: isinstance(x, list)) |
| |
48 |
| |
49 def is_list_of(x, chk): |
| |
50 if x is None: |
| |
51 return True |
| |
52 elif isinstance(x, list): |
| |
53 return reduce(lambda y, z: y and chk(z), x, True) |
| |
54 else: |
| |
55 return False |
| |
56 |
| |
57 def check_list_of_dicts(cfg, field, descr, loc, default=None): |
| |
58 return check_field(cfg, field, descr, loc, default, |
| |
59 lambda x: is_list_of(x, lambda z: isinstance(z, dict))) |
| |
60 |
| |
61 def check_list_of_strings(cfg, field, descr, loc, default=None): |
| |
62 return check_field(cfg, field, descr, loc, default, |
| |
63 lambda x: is_list_of(x, lambda z: isinstance(z, str))) |
| |
64 |
| |
65 def check_nonempty_list_of_strings(cfg, field, descr, loc): |
| |
66 return check_list_of_strings(cfg, field, descr, loc) and cfg[field] |
| |
67 |
| |
68 |
| |
69 def check_nonneg_int(cfg, field, descr, loc, default=None): |
| |
70 return check_field(cfg, field, descr, loc, default, |
| |
71 lambda x: isinstance(x, int) and x>=0) |
| |
72 |
| |
73 def check_field(cfg, field, descr, loc, default, check): |
| |
74 if field in cfg: |
| |
75 tmp=cfg[field] |
| |
76 if not check(tmp): |
| |
77 error("%s is of invalid type for %s" % (field, loc)) |
| |
78 return tmp |
| |
79 else: |
| |
80 if default is not None: |
| |
81 return default |
| |
82 else: |
| |
83 error("%s is not configured for %s" % (field, loc)) |
| |
84 |
| |
85 # |
| |
86 # Conversion of config into command line |
| |
87 # |
| |
88 |
| |
89 def arglistify(args): |
| |
90 flatten=lambda l: [item for sublist in l for item in sublist] |
| |
91 if args is None: |
| |
92 return [] |
| |
93 else: |
| |
94 return flatten([['--' + key, str(d[key])] for d in args for key in d]) |
| |
95 |
| |
96 # |
| |
97 # Load config on module load |
| |
98 # |
| 11 |
99 |
| 12 def expand_env(cfg, env): |
100 def expand_env(cfg, env): |
| 13 if isinstance(cfg, dict): |
101 if isinstance(cfg, dict): |
| 14 out={key: expand_env(val, env) for key, val in cfg.items()} |
102 out={key: expand_env(val, env) for key, val in cfg.items()} |
| 15 elif isinstance(cfg, list): |
103 elif isinstance(cfg, list): |
| 16 out=[expand_env(val, env) for val in cfg] |
104 out=[expand_env(val, env) for val in cfg] |
| 17 elif isinstance(cfg, str): |
105 elif isinstance(cfg, str): |
| 18 out=string.Template(cfg).substitute(os.environ) |
106 out=string.Template(cfg).substitute(os.environ) |
| 19 else: |
107 else: |
| 20 out=cfg |
108 out=cfg |
| 21 |
109 |
| 22 return out |
110 return out |
| 23 |
111 |
| 24 cfgfile=os.path.join(xdg.XDG_CONFIG_HOME, "borgend", "config.yaml") |
112 cfgfile=os.path.join(xdg.XDG_CONFIG_HOME, "borgend", "config.yaml") |
| 25 |
113 |
| 26 if not (os.path.exists(cfgfile) and os.path.isfile(cfgfile)): |
114 if not (os.path.exists(cfgfile) and os.path.isfile(cfgfile)): |
| 27 raise SystemExit(f'Configuration file required: {cfgfile}') |
115 raise SystemExit(f'Configuration file required: {cfgfile}') |
| 28 |
116 |
| 29 with io.open(cfgfile, 'r') as file: |
117 with io.open(cfgfile, 'r') as file: |
| 30 settings=expand_env(yaml.load(file), os.environ); |
118 settings=expand_env(yaml.load(file), os.environ); |
| 31 |
119 |
| |
120 # |
| |
121 # Verify basic settings |
| |
122 # |
| |
123 |
| |
124 if 'borg' not in settings: |
| |
125 settings['borg']=defaults['borg'] |
| |
126 else: |
| |
127 def check_and_set(cfg, field, loc, defa, fn): |
| |
128 cfg[field]=fn(cfg, field, field, loc, defa[field]) |
| |
129 return cfg |
| |
130 |
| |
131 settings['borg']=check_and_set(settings['borg'], 'executable', 'borg', |
| |
132 defaults['borg'], check_string) |
| |
133 |
| |
134 settings['borg']=check_and_set(settings['borg'], 'common_parameters', 'borg', |
| |
135 defaults['borg'], check_list_of_dicts) |
| |
136 |
| |
137 settings['borg']=check_and_set(settings['borg'], 'create_parameters', 'borg', |
| |
138 defaults['borg'], check_list_of_dicts) |
| |
139 |