3
from __future__ import print_function
14
CONFIG_D = '/etc/turku-agent/config.d'
15
SOURCES_D = '/etc/turku-agent/sources.d'
16
SOURCES_SECRETS_D = '/etc/turku-agent/sources_secrets.d'
17
SSH_PRIVATE_KEY = '/etc/turku-agent/id_rsa'
18
SSH_PUBLIC_KEY = '/etc/turku-agent/id_rsa.pub'
19
RSYNCD_CONF = '/etc/turku-agent/rsyncd.conf'
20
RSYNCD_SECRETS = '/etc/turku-agent/rsyncd.secrets'
23
def json_dump_p(obj, f):
24
"""Calls json.dump with standard (pretty) formatting"""
25
return json.dump(obj, f, sort_keys=True, indent=4, separators=(',', ': '))
28
def json_dumps_p(obj):
29
"""Calls json.dumps with standard (pretty) formatting"""
30
return json.dumps(obj, sort_keys=True, indent=4, separators=(',', ': '))
34
"""Recursively merge one dict into another."""
35
if not isinstance(m, dict):
37
out = copy.deepcopy(s)
38
for k, v in m.items():
39
if k in out and isinstance(out[k], dict):
40
out[k] = dict_merge(out[k], v)
42
out[k] = copy.deepcopy(v)
46
def check_directories():
47
for d in (CONFIG_D, SOURCES_D):
48
if not os.path.isdir(d):
50
for d in (SOURCES_SECRETS_D):
51
if not os.path.isdir(d):
55
for f in (SSH_PRIVATE_KEY, SSH_PUBLIC_KEY, RSYNCD_CONF, RSYNCD_SECRETS):
56
d = os.path.dirname(f)
57
if not os.path.isdir(d):
66
# Merge in config.d/*.json to the root level
67
config_files = [os.path.join(CONFIG_D, fn) for fn in os.listdir(CONFIG_D) if fn.endswith('.json') and os.path.isfile(os.path.join(CONFIG_D, fn)) and os.access(os.path.join(CONFIG_D, fn), os.R_OK)]
69
for file in config_files:
72
built_config = dict_merge(built_config, j)
74
# Validate the unit name
75
if not 'unit_name' in built_config:
76
built_config['unit_name'] = platform.node()
77
# If this isn't in the on-disk config, don't write it; just
78
# generate it every time
80
# Validate the machine UUID/secret
81
write_uuid_data = False
82
if not 'machine_uuid' in built_config:
83
built_config['machine_uuid'] = str(uuid.uuid4())
84
write_uuid_data = True
85
if not 'machine_secret' in built_config:
86
built_config['machine_secret'] = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(30))
87
write_uuid_data = True
88
# Write out the machine UUID/secret if needed
90
with open(os.path.join(CONFIG_D, '10-machine_uuid.json'), 'w') as f:
91
os.chmod(os.path.join(CONFIG_D, '10-machine_uuid.json'), 0o600)
92
json_dump_p({'machine_uuid': built_config['machine_uuid'], 'machine_secret': built_config['machine_secret']}, f)
94
# Generate the SSH keypair if it doesn't exist
95
if not os.path.isfile(SSH_PUBLIC_KEY):
96
subprocess.check_call(['ssh-keygen', '-t', 'rsa', '-N', '', '-C', 'turku', '-f', SSH_PRIVATE_KEY])
98
# Pull the SSH public key
99
with open(SSH_PUBLIC_KEY) as f:
100
built_config['ssh_public_key'] = f.read().rstrip()
102
# Merge in sources.d/*.json to the sources dict
103
sources_files = [os.path.join(SOURCES_D, fn) for fn in os.listdir(SOURCES_D) if fn.endswith('.json') and os.path.isfile(os.path.join(SOURCES_D, fn)) and os.access(os.path.join(SOURCES_D, fn), os.R_OK)]
105
for file in sources_files:
106
with open(file) as f:
109
# Ignore incomplete source entries
110
if not 'path' in j[s]:
111
print('WARNING: Path not found for "%s", not using.' % s, file=sys.stderr)
113
built_config = dict_merge(built_config, {'sources': j})
115
for s in built_config['sources']:
116
# Check for missing usernames/passwords
117
if not ('username' in built_config['sources'][s] or 'password' in built_config['sources'][s]):
118
# If they're in sources_secrets.d, use them
119
if os.path.isfile(os.path.join(SOURCES_SECRETS_D, s + '.json')):
120
with open(os.path.join(SOURCES_SECRETS_D, s + '.json')) as f:
122
built_config = dict_merge(built_config, {'sources': {s: j}})
123
# Check again and generate sources_secrets.d if still not found
124
if not ('username' in built_config['sources'][s] or 'password' in built_config['sources'][s]):
125
if not 'username' in built_config['sources'][s]:
126
built_config['sources'][s]['username'] = str(uuid.uuid4())
127
if not 'password' in built_config['sources'][s]:
128
built_config['sources'][s]['password'] = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(30))
129
with open(os.path.join(SOURCES_SECRETS_D, s + '.json'), 'w') as f:
130
json_dump_p({'username': built_config['sources'][s]['username'], 'password': built_config['sources'][s]['password']}, f)
132
#print(json_dumps_p(built_config))
137
def write_conf_files(built_config):
139
built_rsyncd_conf = 'address = 127.0.0.1\nport = 27873\nlog file = /dev/stdout\nuid = root\ngid = root\nlist = false\n\n'
141
for s in built_config['sources']:
142
sd = built_config['sources'][s]
143
rsyncd_secrets.append((sd['username'], sd['password']))
144
built_rsyncd_conf += '[%s]\n path = %s\n auth users = %s\n secrets_file = %s\n read only = true\n\n' % (s, sd['path'], sd['username'], RSYNCD_SECRETS)
145
with open(RSYNCD_CONF, 'w') as f:
146
f.write(built_rsyncd_conf)
148
#print(built_rsyncd_conf)
150
# Build rsyncd.secrets
151
built_rsyncd_secrets = ''
152
for (username, password) in rsyncd_secrets:
153
built_rsyncd_secrets += username + ':' + password + '\n'
154
with open(RSYNCD_SECRETS, 'w') as f:
155
os.chmod(RSYNCD_SECRETS, 0o600)
156
f.write(built_rsyncd_secrets)
158
#print(built_rsyncd_secrets)
161
def restart_services():
163
if not subprocess.call(['service', 'turku-agent-rsyncd', 'restart']) == 0:
164
subprocess.check_call(['service', 'turku-agent-rsyncd', 'start'])
169
built_config = parse_config()
170
write_conf_files(built_config)
174
if __name__ == '__main__':
175
sys.exit(main(sys.argv[1:]))