/
setup.py
executable file
·447 lines (348 loc) · 12.5 KB
/
setup.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
# imports - standard imports
import os
import sys
# imports - third party imports
import click
# imports - module imports
from bench.utils import exec_cmd, run_playbook, which
from bench.utils.cli import SugaredOption
@click.group(help="Setup command group for enabling setting up a Frappe environment")
def setup():
pass
@click.command(
"sudoers", help="Add commands to sudoers list for execution without password"
)
@click.argument("user")
def setup_sudoers(user):
from bench.utils.system import setup_sudoers
setup_sudoers(user)
@click.command("nginx", help="Generate configuration files for NGINX")
@click.option(
"--logging", default="combined", type=click.Choice(["none", "site", "combined"])
)
@click.option(
"--log_format",
help="Specify the log_format for nginx. Use none or '' to not set a value.",
only_if_set=["logging"],
cls=SugaredOption,
default="main",
)
@click.option(
"--yes", help="Yes to regeneration of nginx config file", default=False, is_flag=True
)
def setup_nginx(yes=False, logging="combined", log_format=None):
from bench.config.nginx import make_nginx_conf
make_nginx_conf(bench_path=".", yes=yes, logging=logging, log_format=log_format)
@click.command("reload-nginx", help="Checks NGINX config file and reloads service")
def reload_nginx():
from bench.config.production_setup import reload_nginx
reload_nginx()
@click.command("supervisor", help="Generate configuration for supervisor")
@click.option("--user", help="optional user argument")
@click.option(
"--yes", help="Yes to regeneration of supervisor config", is_flag=True, default=False
)
@click.option(
"--skip-redis", help="Skip redis configuration", is_flag=True, default=False
)
@click.option(
"--skip-supervisord",
help="Skip supervisord configuration",
is_flag=True,
default=False,
)
def setup_supervisor(user=None, yes=False, skip_redis=False, skip_supervisord=False):
from bench.utils import get_cmd_output
from bench.config.supervisor import (
check_supervisord_config,
generate_supervisor_config,
)
if which("supervisorctl") is None:
click.secho("Please install `supervisor` to proceed", fg="red")
sys.exit(1)
if not skip_supervisord and "Permission denied" in get_cmd_output(
"supervisorctl status"
):
check_supervisord_config(user=user)
generate_supervisor_config(bench_path=".", user=user, yes=yes, skip_redis=skip_redis)
@click.command("redis", help="Generates configuration for Redis")
def setup_redis():
from bench.config.redis import generate_config
generate_config(".")
@click.command("fonts", help="Add Frappe fonts to system")
def setup_fonts():
from bench.utils.system import setup_fonts
setup_fonts()
@click.command(
"production", help="Setup Frappe production environment for specific user"
)
@click.argument("user")
@click.option("--yes", help="Yes to regeneration config", is_flag=True, default=False)
def setup_production(user, yes=False):
from bench.config.production_setup import setup_production
setup_production(user=user, yes=yes)
@click.command("backups", help="Add cronjob for bench backups")
def setup_backups():
from bench.bench import Bench
Bench(".").setup.backups()
@click.command("env", help="Setup Python environment for bench")
@click.option(
"--python", type=str, default="python3", help="Path to Python Executable."
)
def setup_env(python="python3"):
from bench.bench import Bench
return Bench(".").setup.env(python=python)
@click.command("firewall", help="Setup firewall for system")
@click.option("--ssh_port")
@click.option("--force")
def setup_firewall(ssh_port=None, force=False):
if not force:
click.confirm(
f"Setting up the firewall will block all ports except 80, 443 and {ssh_port}\nDo you want to continue?",
abort=True,
)
if not ssh_port:
ssh_port = 22
run_playbook("roles/bench/tasks/setup_firewall.yml", {"ssh_port": ssh_port})
@click.command("ssh-port", help="Set SSH Port for system")
@click.argument("port")
@click.option("--force")
def set_ssh_port(port, force=False):
if not force:
click.confirm(
f"This will change your SSH Port to {port}\nDo you want to continue?", abort=True
)
run_playbook("roles/bench/tasks/change_ssh_port.yml", {"ssh_port": port})
@click.command("lets-encrypt", help="Setup lets-encrypt SSL for site")
@click.argument("site")
@click.option("--custom-domain")
@click.option(
"-n",
"--non-interactive",
default=False,
is_flag=True,
help="Run command non-interactively. This flag restarts nginx and runs certbot non interactively. Shouldn't be used on 1'st attempt",
)
def setup_letsencrypt(site, custom_domain, non_interactive):
from bench.config.lets_encrypt import setup_letsencrypt
setup_letsencrypt(site, custom_domain, bench_path=".", interactive=not non_interactive)
@click.command(
"wildcard-ssl", help="Setup wildcard SSL certificate for multi-tenant bench"
)
@click.argument("domain")
@click.option("--email")
@click.option(
"--exclude-base-domain",
default=False,
is_flag=True,
help="SSL Certificate not applicable for base domain",
)
def setup_wildcard_ssl(domain, email, exclude_base_domain):
from bench.config.lets_encrypt import setup_wildcard_ssl
setup_wildcard_ssl(
domain, email, bench_path=".", exclude_base_domain=exclude_base_domain
)
@click.command("procfile", help="Generate Procfile for bench start")
def setup_procfile():
from bench.config.procfile import setup_procfile
setup_procfile(".")
@click.command(
"socketio", help="[DEPRECATED] Setup node dependencies for socketio server"
)
def setup_socketio():
return
@click.command("requirements")
@click.option("--node", help="Update only Node packages", default=False, is_flag=True)
@click.option(
"--python", help="Update only Python packages", default=False, is_flag=True
)
@click.option(
"--dev",
help="Install optional python development dependencies",
default=False,
is_flag=True,
)
@click.argument("apps", nargs=-1)
def setup_requirements(node=False, python=False, dev=False, apps=None):
"""
Setup Python and Node dependencies.
You can optionally specify one or more apps to setup dependencies for.
"""
from bench.bench import Bench
bench = Bench(".")
if not (node or python or dev):
bench.setup.requirements(apps=apps)
elif not node and not dev:
bench.setup.python(apps=apps)
elif not python and not dev:
bench.setup.node(apps=apps)
else:
from bench.utils.bench import install_python_dev_dependencies
install_python_dev_dependencies(apps=apps)
if node:
click.secho(
"--dev flag only supports python dependencies. All node development dependencies are installed by default.",
fg="yellow",
)
@click.command(
"manager",
help="Setup bench-manager.local site with the bench_manager app installed on it",
)
@click.option(
"--yes", help="Yes to regeneration of nginx config file", default=False, is_flag=True
)
@click.option(
"--port", help="Port on which you want to run bench manager", default=23624
)
@click.option("--domain", help="Domain on which you want to run bench manager")
def setup_manager(yes=False, port=23624, domain=None):
from bench.bench import Bench
from bench.config.nginx import make_bench_manager_nginx_conf
create_new_site = True
if "bench-manager.local" in os.listdir("sites"):
create_new_site = click.confirm("Site already exists. Overwrite existing site?")
if create_new_site:
exec_cmd("bench new-site --force bench-manager.local")
if "bench_manager" in os.listdir("apps"):
print("App already exists. Skipping app download.")
else:
exec_cmd("bench get-app bench_manager")
exec_cmd("bench --site bench-manager.local install-app bench_manager")
bench_path = "."
bench = Bench(bench_path)
if bench.conf.get("restart_supervisor_on_update") or bench.conf.get(
"restart_systemd_on_update"
):
# implicates a production setup or so I presume
if not domain:
print(
"Please specify the site name on which you want to host bench-manager using the 'domain' flag"
)
sys.exit(1)
if domain not in bench.sites:
raise Exception("No such site")
make_bench_manager_nginx_conf(bench_path, yes=yes, port=port, domain=domain)
@click.command("config", help="Generate or over-write sites/common_site_config.json")
def setup_config():
from bench.config.common_site_config import setup_config
setup_config(".")
@click.command("add-domain", help="Add a custom domain to a particular site")
@click.argument("domain")
@click.option("--site", prompt=True)
@click.option("--ssl-certificate", help="Absolute path to SSL Certificate")
@click.option("--ssl-certificate-key", help="Absolute path to SSL Certificate Key")
def add_domain(domain, site=None, ssl_certificate=None, ssl_certificate_key=None):
"""Add custom domain to site"""
if not site:
print("Please specify site")
sys.exit(1)
from bench.config.site_config import add_domain
add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path=".")
@click.command("remove-domain", help="Remove custom domain from a site")
@click.argument("domain")
@click.option("--site", prompt=True)
def remove_domain(domain, site=None):
if not site:
print("Please specify site")
sys.exit(1)
from bench.config.site_config import remove_domain
remove_domain(site, domain, bench_path=".")
@click.command(
"sync-domains",
help="Check if there is a change in domains. If yes, updates the domains list.",
)
@click.option("--domain", multiple=True)
@click.option("--site", prompt=True)
def sync_domains(domain=None, site=None):
if not site:
print("Please specify site")
sys.exit(1)
try:
domains = list(map(str, domain))
except Exception:
print("Domains should be a json list of strings or dictionaries")
sys.exit(1)
from bench.config.site_config import sync_domains
changed = sync_domains(site, domains, bench_path=".")
# if changed, success, else failure
sys.exit(0 if changed else 1)
@click.command("role", help="Install dependencies via ansible roles")
@click.argument("role")
@click.option("--admin_emails", default="")
@click.option("--mysql_root_password", "--mariadb_root_password")
@click.option("--container", is_flag=True, default=False)
def setup_roles(role, **kwargs):
extra_vars = {"production": True}
extra_vars.update(kwargs)
if role:
run_playbook("site.yml", extra_vars=extra_vars, tag=role)
else:
run_playbook("site.yml", extra_vars=extra_vars)
@click.command(
"fail2ban",
help="Setup fail2ban, an intrusion prevention software framework that protects computer servers from brute-force attacks",
)
@click.option(
"--maxretry",
default=6,
help="Number of matches (i.e. value of the counter) which triggers ban action on the IP. Default is 6 seconds",
)
@click.option(
"--bantime",
default=600,
help="Duration (in seconds) for IP to be banned for. Negative number for 'permanent' ban. Default is 600 seconds",
)
@click.option(
"--findtime",
default=600,
help="The counter is set to zero if match found within 'findtime' seconds doesn't exceed 'maxretry'. Default is 600 seconds",
)
def setup_nginx_proxy_jail(**kwargs):
run_playbook("roles/fail2ban/tasks/configure_nginx_jail.yml", extra_vars=kwargs)
@click.command("systemd", help="Generate configuration for systemd")
@click.option("--user", help="Optional user argument")
@click.option(
"--yes",
help="Yes to regeneration of systemd config files",
is_flag=True,
default=False,
)
@click.option("--stop", help="Stop bench services", is_flag=True, default=False)
@click.option("--create-symlinks", help="Create Symlinks", is_flag=True, default=False)
@click.option("--delete-symlinks", help="Delete Symlinks", is_flag=True, default=False)
def setup_systemd(
user=None, yes=False, stop=False, create_symlinks=False, delete_symlinks=False
):
from bench.config.systemd import generate_systemd_config
generate_systemd_config(
bench_path=".",
user=user,
yes=yes,
stop=stop,
create_symlinks=create_symlinks,
delete_symlinks=delete_symlinks,
)
setup.add_command(setup_sudoers)
setup.add_command(setup_nginx)
setup.add_command(reload_nginx)
setup.add_command(setup_supervisor)
setup.add_command(setup_redis)
setup.add_command(setup_letsencrypt)
setup.add_command(setup_wildcard_ssl)
setup.add_command(setup_production)
setup.add_command(setup_backups)
setup.add_command(setup_env)
setup.add_command(setup_procfile)
setup.add_command(setup_socketio)
setup.add_command(setup_requirements)
setup.add_command(setup_manager)
setup.add_command(setup_config)
setup.add_command(setup_fonts)
setup.add_command(add_domain)
setup.add_command(remove_domain)
setup.add_command(sync_domains)
setup.add_command(setup_firewall)
setup.add_command(set_ssh_port)
setup.add_command(setup_roles)
setup.add_command(setup_nginx_proxy_jail)
setup.add_command(setup_systemd)