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 |