4 |
5 | Sorry! We will be back soon.
6 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 | Sorry!
77 | We will be back soon.
78 |
79 |
80 | Don't panic. It's not you, it's us.
81 | Most likely, our engineers are updating the code,
82 | and it should take a minute for the new code to load into memory.
83 | Try refreshing after a minute or two.
84 |
85 |
86 |
87 |
88 |
89 |
90 |
--------------------------------------------------------------------------------
/bench/config/templates/Procfile:
--------------------------------------------------------------------------------
1 | {% if not skip_redis %}
2 | redis_cache: redis-server config/redis_cache.conf
3 | redis_queue: redis-server config/redis_queue.conf
4 | {% endif %}
5 | {% if not skip_web %}
6 | web: bench serve {% if with_coverage -%} --with-coverage {%- endif %} {% if webserver_port -%} --port {{ webserver_port }} {%- endif %}
7 | {% endif %}
8 | {% if not skip_socketio %}
9 | socketio: {{ node }} apps/frappe/socketio.js
10 | {% endif %}
11 | {% if not skip_watch %}
12 | watch: bench watch
13 | {% endif %}
14 | {% if not skip_schedule %}
15 | schedule: bench schedule
16 | {% endif %}
17 | worker: {{ 'OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES NO_PROXY=*' if is_mac else '' }} bench worker 1>> logs/worker.log 2>> logs/worker.error.log
18 | {% for worker_name, worker_details in workers.items() %}
19 | worker_{{ worker_name }}: {{ 'OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES NO_PROXY=*' if is_mac else '' }} bench worker --queue {{ worker_name }} 1>> logs/worker.log 2>> logs/worker.error.log
20 | {% endfor %}
21 |
22 |
--------------------------------------------------------------------------------
/bench/config/templates/bench_manager_nginx.conf:
--------------------------------------------------------------------------------
1 | server {
2 | listen {{ port }};
3 | server_name {{ domain }};
4 | root {{ sites_path }};
5 |
6 |
7 | {% if ssl_certificate and ssl_certificate_key %}
8 | ssl on;
9 | ssl_certificate {{ ssl_certificate }};
10 | ssl_certificate_key {{ ssl_certificate_key }};
11 | ssl_session_timeout 5m;
12 | ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
13 | ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH+aRSA+RC4 EECDH EDH+aRSA RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
14 | ssl_prefer_server_ciphers on;
15 | {% endif %}
16 |
17 | location /assets {
18 | try_files $uri =404;
19 | }
20 |
21 | location ~ ^/protected/(.*) {
22 | internal;
23 | try_files /{{ bench_manager_site_name }}/$1 =404;
24 | }
25 |
26 | location /socket.io {
27 | proxy_http_version 1.1;
28 | proxy_set_header Upgrade $http_upgrade;
29 | proxy_set_header Connection "upgrade";
30 | proxy_set_header X-Frappe-Site-Name {{ bench_manager_site_name }};
31 | proxy_set_header Origin $scheme://$http_host;
32 | proxy_set_header Host {{ bench_manager_site_name }};
33 |
34 | proxy_pass http://{{ bench_name }}-socketio-server;
35 | }
36 |
37 | location / {
38 | try_files /{{ bench_manager_site_name }}/public/$uri @webserver;
39 | }
40 |
41 | location @webserver {
42 | proxy_set_header X-Forwarded-For $remote_addr;
43 | proxy_set_header X-Forwarded-Proto $scheme;
44 | proxy_set_header X-Frappe-Site-Name {{ bench_manager_site_name }};
45 | proxy_set_header Host {{ bench_manager_site_name }};
46 | proxy_set_header X-Use-X-Accel-Redirect True;
47 | proxy_read_timeout {{ http_timeout or 120 }};
48 | proxy_redirect off;
49 |
50 | proxy_pass http://{{ bench_name }}-frappe;
51 | }
52 |
53 | # error pages
54 | {% for error_code, error_page in error_pages.items() -%}
55 |
56 | error_page {{ error_code }} /{{ error_page.split('/')[-1] }};
57 | location /{{ error_code }}.html {
58 | root {{ '/'.join(error_page.split('/')[:-1]) }};
59 | internal;
60 | }
61 |
62 | {% endfor -%}
63 |
64 | # optimizations
65 | sendfile on;
66 | keepalive_timeout 15;
67 | client_max_body_size 50m;
68 | client_body_buffer_size 16K;
69 | client_header_buffer_size 1k;
70 |
71 | # enable gzip compresion
72 | # based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
73 | gzip on;
74 | gzip_http_version 1.1;
75 | gzip_comp_level 5;
76 | gzip_min_length 256;
77 | gzip_proxied any;
78 | gzip_vary on;
79 | gzip_types
80 | application/atom+xml
81 | application/javascript
82 | application/json
83 | application/rss+xml
84 | application/vnd.ms-fontobject
85 | application/x-font-ttf
86 | application/font-woff
87 | application/x-web-app-manifest+json
88 | application/xhtml+xml
89 | application/xml
90 | font/opentype
91 | image/svg+xml
92 | image/x-icon
93 | text/css
94 | text/plain
95 | text/x-component
96 | ;
97 | # text/html is always compressed by HttpGzipModule
98 | }
99 |
100 |
101 |
--------------------------------------------------------------------------------
/bench/config/templates/frappe_sudoers:
--------------------------------------------------------------------------------
1 | # This file is auto-generated by frappe/bench
2 | # To re-generate this file, run "bench setup sudoers"
3 |
4 | {% if service %}
5 | {{ user }} ALL = (root) {{ service }}
6 | {{ user }} ALL = (root) NOPASSWD: {{ service }} nginx *
7 | {% endif %}
8 |
9 | {% if systemctl %}
10 | {{ user }} ALL = (root) {{ systemctl }}
11 | {{ user }} ALL = (root) NOPASSWD: {{ systemctl }} * nginx
12 | {% endif %}
13 |
14 | {% if nginx %}
15 | {{ user }} ALL = (root) NOPASSWD: {{ nginx }}
16 | {% endif %}
17 |
18 | {{ user }} ALL = (root) NOPASSWD: {{ certbot }}
19 | Defaults:{{ user }} !requiretty
20 |
21 |
--------------------------------------------------------------------------------
/bench/config/templates/letsencrypt.cfg:
--------------------------------------------------------------------------------
1 | # This is an example of the kind of things you can do in a configuration file.
2 | # All flags used by the client can be configured here. Run Certbot with
3 | # "--help" to learn more about the available options.
4 |
5 | # Use a 4096 bit RSA key instead of 2048
6 | rsa-key-size = 4096
7 |
8 | # Uncomment and update to register with the specified e-mail address
9 | #email = email@domain.com
10 |
11 | # Uncomment and update to generate certificates for the specified
12 | # domains.
13 | domains = {{ domain }}
14 |
15 | # Uncomment to use a text interface instead of ncurses
16 | text = True
17 |
18 | # Uncomment to use the standalone authenticator on port 443
19 | authenticator = standalone
20 |
--------------------------------------------------------------------------------
/bench/config/templates/nginx_default.conf:
--------------------------------------------------------------------------------
1 | # For more information on configuration, see:
2 | # * Official English Documentation: http://nginx.org/en/docs/
3 | # * Official Russian Documentation: http://nginx.org/ru/docs/
4 |
5 | user nginx;
6 | worker_processes 1;
7 |
8 | error_log /var/log/nginx/error.log;
9 | #error_log /var/log/nginx/error.log notice;
10 | #error_log /var/log/nginx/error.log info;
11 |
12 | pid /run/nginx.pid;
13 |
14 |
15 | events {
16 | worker_connections 1024;
17 | }
18 |
19 |
20 | http {
21 | include /etc/nginx/mime.types;
22 | default_type application/octet-stream;
23 |
24 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
25 | '$status $body_bytes_sent "$http_referer" '
26 | '"$http_user_agent" "$http_x_forwarded_for"';
27 |
28 | access_log /var/log/nginx/access.log main;
29 |
30 | sendfile on;
31 | #tcp_nopush on;
32 |
33 | #keepalive_timeout 0;
34 | keepalive_timeout 65;
35 |
36 | server_names_hash_bucket_size 64;
37 |
38 | #gzip on;
39 |
40 | index index.html index.htm;
41 |
42 | # Load modular configuration files from the /etc/nginx/conf.d directory.
43 | # See http://nginx.org/en/docs/ngx_core_module.html#include
44 | # for more information.
45 | include /etc/nginx/conf.d/*.conf;
46 | }
47 |
--------------------------------------------------------------------------------
/bench/config/templates/redis_cache.conf:
--------------------------------------------------------------------------------
1 | dbfilename redis_cache.rdb
2 | dir {{ pid_path }}
3 | pidfile {{ pid_path }}/redis_cache.pid
4 | bind 127.0.0.1
5 | port {{ port }}
6 | maxmemory {{ maxmemory }}mb
7 | maxmemory-policy allkeys-lru
8 | appendonly no
9 | {% if redis_version and redis_version >= 2.2 %}
10 | save ""
11 | {% endif %}
12 | {% if redis_version and redis_version >= 6.0 %}
13 | aclfile {{ config_path }}/redis_cache.acl
14 | {% endif %}
15 |
--------------------------------------------------------------------------------
/bench/config/templates/redis_queue.conf:
--------------------------------------------------------------------------------
1 | dbfilename redis_queue.rdb
2 | dir {{ pid_path }}
3 | pidfile {{ pid_path }}/redis_queue.pid
4 | bind 127.0.0.1
5 | port {{ port }}
6 | {% if redis_version and redis_version >= 6.0 %}
7 | aclfile {{ config_path }}/redis_queue.acl
8 | {% endif %}
9 |
--------------------------------------------------------------------------------
/bench/config/templates/supervisor.conf:
--------------------------------------------------------------------------------
1 | ; Notes:
2 | ; priority=1 --> Lower priorities indicate programs that start first and shut down last
3 | ; killasgroup=true --> send kill signal to child processes too
4 |
5 | ; graceful timeout should always be lower than stopwaitsecs to avoid orphan gunicorn workers.
6 | [program:{{ bench_name }}-frappe-web]
7 | command={{ bench_dir }}/env/bin/gunicorn -b 127.0.0.1:{{ webserver_port }} -w {{ gunicorn_workers }} --max-requests {{ gunicorn_max_requests }} --max-requests-jitter {{ gunicorn_max_requests_jitter }} -t {{ http_timeout }} --graceful-timeout 30 frappe.app:application --preload
8 | priority=4
9 | autostart=true
10 | autorestart=true
11 | stdout_logfile={{ bench_dir }}/logs/web.log
12 | stderr_logfile={{ bench_dir }}/logs/web.error.log
13 | stopwaitsecs=40
14 | killasgroup=true
15 | user={{ user }}
16 | directory={{ sites_dir }}
17 | startretries={{ supervisor_startretries }}
18 |
19 | [program:{{ bench_name }}-frappe-schedule]
20 | command={{ bench_cmd }} schedule
21 | priority=3
22 | autostart=true
23 | stdout_logfile={{ bench_dir }}/logs/schedule.log
24 | stderr_logfile={{ bench_dir }}/logs/schedule.error.log
25 | user={{ user }}
26 | directory={{ bench_dir }}
27 | startretries={{ supervisor_startretries }}
28 |
29 | {% if not multi_queue_consumption %}
30 | [program:{{ bench_name }}-frappe-default-worker]
31 | command={{ bench_cmd }} worker --queue default
32 | priority=4
33 | autostart=true
34 | autorestart=true
35 | stdout_logfile={{ bench_dir }}/logs/worker.log
36 | stderr_logfile={{ bench_dir }}/logs/worker.error.log
37 | user={{ user }}
38 | stopwaitsecs=1560
39 | directory={{ bench_dir }}
40 | killasgroup=true
41 | numprocs={{ background_workers }}
42 | process_name=%(program_name)s-%(process_num)d
43 | startretries={{ supervisor_startretries }}
44 | {% endif %}
45 |
46 | [program:{{ bench_name }}-frappe-short-worker]
47 | command={{ bench_cmd }} worker --queue short{{',default' if multi_queue_consumption else ''}}
48 | priority=4
49 | autostart=true
50 | autorestart=true
51 | stdout_logfile={{ bench_dir }}/logs/worker.log
52 | stderr_logfile={{ bench_dir }}/logs/worker.error.log
53 | user={{ user }}
54 | stopwaitsecs=360
55 | directory={{ bench_dir }}
56 | killasgroup=true
57 | numprocs={{ background_workers }}
58 | process_name=%(program_name)s-%(process_num)d
59 | startretries={{ supervisor_startretries }}
60 |
61 | [program:{{ bench_name }}-frappe-long-worker]
62 | command={{ bench_cmd }} worker --queue long{{',default,short' if multi_queue_consumption else ''}}
63 | priority=4
64 | autostart=true
65 | autorestart=true
66 | stdout_logfile={{ bench_dir }}/logs/worker.log
67 | stderr_logfile={{ bench_dir }}/logs/worker.error.log
68 | user={{ user }}
69 | stopwaitsecs=1560
70 | directory={{ bench_dir }}
71 | killasgroup=true
72 | numprocs={{ background_workers }}
73 | process_name=%(program_name)s-%(process_num)d
74 | startretries={{ supervisor_startretries }}
75 |
76 | {% for worker_name, worker_details in workers.items() %}
77 | [program:{{ bench_name }}-frappe-{{ worker_name }}-worker]
78 | command={{ bench_cmd }} worker --queue {{ worker_name }}
79 | priority=4
80 | autostart=true
81 | autorestart=true
82 | stdout_logfile={{ bench_dir }}/logs/worker.log
83 | stderr_logfile={{ bench_dir }}/logs/worker.error.log
84 | user={{ user }}
85 | stopwaitsecs={{ worker_details["timeout"] }}
86 | directory={{ bench_dir }}
87 | killasgroup=true
88 | numprocs={{ worker_details["background_workers"] or background_workers }}
89 | process_name=%(program_name)s-%(process_num)d
90 | startretries={{ supervisor_startretries }}
91 | {% endfor %}
92 |
93 |
94 | {% if not skip_redis %}
95 | [program:{{ bench_name }}-redis-cache]
96 | command={{ redis_server }} {{ redis_cache_config }}
97 | priority=1
98 | autostart=true
99 | autorestart=true
100 | stdout_logfile={{ bench_dir }}/logs/redis-cache.log
101 | stderr_logfile={{ bench_dir }}/logs/redis-cache.error.log
102 | user={{ user }}
103 | directory={{ sites_dir }}
104 | startretries={{ supervisor_startretries }}
105 |
106 | [program:{{ bench_name }}-redis-queue]
107 | command={{ redis_server }} {{ redis_queue_config }}
108 | priority=1
109 | autostart=true
110 | autorestart=true
111 | stdout_logfile={{ bench_dir }}/logs/redis-queue.log
112 | stderr_logfile={{ bench_dir }}/logs/redis-queue.error.log
113 | user={{ user }}
114 | directory={{ sites_dir }}
115 | startretries={{ supervisor_startretries }}
116 | {% endif %}
117 |
118 | {% if node %}
119 | [program:{{ bench_name }}-node-socketio]
120 | command={{ node }} {{ bench_dir }}/apps/frappe/socketio.js
121 | priority=4
122 | autostart=true
123 | autorestart=true
124 | stdout_logfile={{ bench_dir }}/logs/node-socketio.log
125 | stderr_logfile={{ bench_dir }}/logs/node-socketio.error.log
126 | user={{ user }}
127 | directory={{ bench_dir }}
128 | startretries={{ supervisor_startretries }}
129 | {% endif %}
130 |
131 | [group:{{ bench_name }}-web]
132 | programs={{ bench_name }}-frappe-web {%- if node -%} ,{{ bench_name }}-node-socketio {%- endif%}
133 |
134 |
135 | {% if multi_queue_consumption %}
136 |
137 | [group:{{ bench_name }}-workers]
138 | programs={{ bench_name }}-frappe-schedule,{{ bench_name }}-frappe-short-worker,{{ bench_name }}-frappe-long-worker{%- for worker_name in workers -%},{{ bench_name }}-frappe-{{ worker_name }}-worker{%- endfor %}
139 |
140 | {% else %}
141 |
142 | [group:{{ bench_name }}-workers]
143 | programs={{ bench_name }}-frappe-schedule,{{ bench_name }}-frappe-default-worker,{{ bench_name }}-frappe-short-worker,{{ bench_name }}-frappe-long-worker{%- for worker_name in workers -%},{{ bench_name }}-frappe-{{ worker_name }}-worker{%- endfor %}
144 |
145 | {% endif %}
146 |
147 | {% if not skip_redis %}
148 | [group:{{ bench_name }}-redis]
149 | programs={{ bench_name }}-redis-cache,{{ bench_name }}-redis-queue
150 | {% endif %}
151 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-frappe-default-worker.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-frappe-default-worker %I"
3 | PartOf={{ bench_name }}-workers.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ bench_cmd }} worker --queue default
10 | StandardOutput=file:{{ bench_dir }}/logs/worker.log
11 | StandardError=file:{{ bench_dir }}/logs/worker.error.log
12 | WorkingDirectory={{ bench_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-frappe-long-worker.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-frappe-short-worker %I"
3 | PartOf={{ bench_name }}-workers.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ bench_cmd }} worker --queue long
10 | StandardOutput=file:{{ bench_dir }}/logs/worker.log
11 | StandardError=file:{{ bench_dir }}/logs/worker.error.log
12 | WorkingDirectory={{ bench_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-frappe-schedule.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-frappe-schedule"
3 | PartOf={{ bench_name }}-workers.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ bench_cmd }} schedule
10 | StandardOutput=file:{{ bench_dir }}/logs/schedule.log
11 | StandardError=file:{{ bench_dir }}/logs/schedule.error.log
12 | WorkingDirectory={{ bench_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-frappe-short-worker.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-frappe-short-worker %I"
3 | PartOf={{ bench_name }}-workers.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ bench_cmd }} worker --queue short
10 | StandardOutput=file:{{ bench_dir }}/logs/worker.log
11 | StandardError=file:{{ bench_dir }}/logs/worker.error.log
12 | WorkingDirectory={{ bench_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-frappe-web.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-frappe-web"
3 | PartOf={{ bench_name }}-web.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ bench_dir }}/env/bin/gunicorn -b 127.0.0.1:{{ webserver_port }} -w {{ gunicorn_workers }} -t {{ http_timeout }} --max-requests {{ gunicorn_max_requests }} --max-requests-jitter {{ gunicorn_max_requests_jitter }} frappe.app:application --preload
10 | StandardOutput=file:{{ bench_dir }}/logs/web.log
11 | StandardError=file:{{ bench_dir }}/logs/web.error.log
12 | WorkingDirectory={{ sites_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-node-socketio.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | After={{ bench_name }}-frappe-web.service
3 | Description="{{ bench_name }}-node-socketio"
4 | PartOf={{ bench_name }}-web.target
5 |
6 | [Service]
7 | User={{ user }}
8 | Group={{ user }}
9 | Restart=always
10 | ExecStart={{ node }} {{ bench_dir }}/apps/frappe/socketio.js
11 | StandardOutput=file:{{ bench_dir }}/logs/node-socketio.log
12 | StandardError=file:{{ bench_dir }}/logs/node-socketio.error.log
13 | WorkingDirectory={{ bench_dir }}
14 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-redis-cache.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-redis-cache"
3 | PartOf={{ bench_name }}-redis.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ redis_server }} {{ redis_cache_config }}
10 | StandardOutput=file:{{ bench_dir }}/logs/redis-cache.log
11 | StandardError=file:{{ bench_dir }}/logs/redis-cache.error.log
12 | WorkingDirectory={{ sites_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-redis-queue.service:
--------------------------------------------------------------------------------
1 | [Unit]
2 | Description="{{ bench_name }}-redis-queue"
3 | PartOf={{ bench_name }}-redis.target
4 |
5 | [Service]
6 | User={{ user }}
7 | Group={{ user }}
8 | Restart=always
9 | ExecStart={{ redis_server }} {{ redis_queue_config }}
10 | StandardOutput=file:{{ bench_dir }}/logs/redis-queue.log
11 | StandardError=file:{{ bench_dir }}/logs/redis-queue.error.log
12 | WorkingDirectory={{ sites_dir }}
13 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-redis.target:
--------------------------------------------------------------------------------
1 | [Unit]
2 | After=network.target
3 | Wants={{ bench_name }}-redis-cache.service {{ bench_name }}-redis-queue.service
4 |
5 | [Install]
6 | WantedBy=multi-user.target
7 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-web.target:
--------------------------------------------------------------------------------
1 | [Unit]
2 | After=network.target
3 | Wants={{ bench_name }}-frappe-web.service {{ bench_name }}-node-socketio.service
4 |
5 | [Install]
6 | WantedBy=multi-user.target
7 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench-workers.target:
--------------------------------------------------------------------------------
1 | [Unit]
2 | After=network.target
3 | Wants={{ worker_target_wants }}
4 |
5 | [Install]
6 | WantedBy=multi-user.target
7 |
--------------------------------------------------------------------------------
/bench/config/templates/systemd/frappe-bench.target:
--------------------------------------------------------------------------------
1 | [Unit]
2 | After=network.target
3 | Requires={{ bench_name }}-web.target {{ bench_name }}-workers.target {{ bench_name }}-redis.target
4 |
5 | [Install]
6 | WantedBy=multi-user.target
7 |
--------------------------------------------------------------------------------
/bench/exceptions.py:
--------------------------------------------------------------------------------
1 | class InvalidBranchException(Exception):
2 | pass
3 |
4 |
5 | class InvalidRemoteException(Exception):
6 | pass
7 |
8 |
9 | class PatchError(Exception):
10 | pass
11 |
12 |
13 | class CommandFailedError(Exception):
14 | pass
15 |
16 |
17 | class BenchNotFoundError(Exception):
18 | pass
19 |
20 |
21 | class ValidationError(Exception):
22 | pass
23 |
24 |
25 | class AppNotInstalledError(ValidationError):
26 | pass
27 |
28 |
29 | class CannotUpdateReleaseBench(ValidationError):
30 | pass
31 |
32 |
33 | class FeatureDoesNotExistError(CommandFailedError):
34 | pass
35 |
36 | class VersionNotFound(Exception):
37 | pass
38 |
--------------------------------------------------------------------------------
/bench/patches/__init__.py:
--------------------------------------------------------------------------------
1 | import os
2 | import importlib
3 |
4 |
5 | def run(bench_path):
6 | source_patch_file = os.path.join(
7 | os.path.dirname(os.path.abspath(__file__)), "patches.txt"
8 | )
9 | target_patch_file = os.path.join(os.path.abspath(bench_path), "patches.txt")
10 |
11 | with open(source_patch_file) as f:
12 | patches = [
13 | p.strip()
14 | for p in f.read().splitlines()
15 | if p.strip() and not p.strip().startswith("#")
16 | ]
17 |
18 | executed_patches = []
19 | if os.path.exists(target_patch_file):
20 | with open(target_patch_file) as f:
21 | executed_patches = f.read().splitlines()
22 |
23 | try:
24 | for patch in patches:
25 | if patch not in executed_patches:
26 | module = importlib.import_module(patch.split()[0])
27 | execute = getattr(module, "execute")
28 | result = execute(bench_path)
29 |
30 | if not result:
31 | executed_patches.append(patch)
32 |
33 | finally:
34 | with open(target_patch_file, "w") as f:
35 | f.write("\n".join(executed_patches))
36 |
37 | # end with an empty line
38 | f.write("\n")
39 |
--------------------------------------------------------------------------------
/bench/patches/patches.txt:
--------------------------------------------------------------------------------
1 | bench.patches.v3.deprecate_old_config
2 | bench.patches.v3.celery_to_rq
3 | bench.patches.v3.redis_bind_ip
4 | bench.patches.v4.update_node
5 | bench.patches.v4.update_socketio
6 | bench.patches.v4.install_yarn #2
7 | bench.patches.v5.fix_user_permissions
8 | bench.patches.v5.fix_backup_cronjob
9 | bench.patches.v5.set_live_reload_config
10 | bench.patches.v5.update_archived_sites
--------------------------------------------------------------------------------
/bench/patches/v5/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frappe/bench/d771849e8ce16e85351904abbf0197c24bda8bfc/bench/patches/v5/__init__.py
--------------------------------------------------------------------------------
/bench/patches/v5/fix_backup_cronjob.py:
--------------------------------------------------------------------------------
1 | from bench.config.common_site_config import get_config
2 | from crontab import CronTab
3 |
4 |
5 | def execute(bench_path):
6 | """
7 | This patch fixes a cron job that would backup sites every minute per 6 hours
8 | """
9 |
10 | user = get_config(bench_path=bench_path).get("frappe_user")
11 | user_crontab = CronTab(user=user)
12 |
13 | for job in user_crontab.find_comment("bench auto backups set for every 6 hours"):
14 | job.every(6).hours()
15 | user_crontab.write()
16 |
--------------------------------------------------------------------------------
/bench/patches/v5/fix_user_permissions.py:
--------------------------------------------------------------------------------
1 | # imports - standard imports
2 | import getpass
3 | import os
4 | import subprocess
5 |
6 | # imports - module imports
7 | from bench.cli import change_uid_msg
8 | from bench.config.production_setup import get_supervisor_confdir, is_centos7, service
9 | from bench.config.common_site_config import get_config
10 | from bench.utils import exec_cmd, get_bench_name, get_cmd_output
11 |
12 |
13 | def is_sudoers_set():
14 | """Check if bench sudoers is set"""
15 | cmd = ["sudo", "-n", "bench"]
16 | bench_warn = False
17 |
18 | with open(os.devnull, "wb") as f:
19 | return_code_check = not subprocess.call(cmd, stdout=f)
20 |
21 | if return_code_check:
22 | try:
23 | bench_warn = change_uid_msg in get_cmd_output(cmd, _raise=False)
24 | except subprocess.CalledProcessError:
25 | bench_warn = False
26 | finally:
27 | return_code_check = return_code_check and bench_warn
28 |
29 | return return_code_check
30 |
31 |
32 | def is_production_set(bench_path):
33 | """Check if production is set for current bench"""
34 | production_setup = False
35 | bench_name = get_bench_name(bench_path)
36 |
37 | supervisor_conf_extn = "ini" if is_centos7() else "conf"
38 | supervisor_conf_file_name = f"{bench_name}.{supervisor_conf_extn}"
39 | supervisor_conf = os.path.join(get_supervisor_confdir(), supervisor_conf_file_name)
40 |
41 | if os.path.exists(supervisor_conf):
42 | production_setup = production_setup or True
43 |
44 | nginx_conf = f"/etc/nginx/conf.d/{bench_name}.conf"
45 |
46 | if os.path.exists(nginx_conf):
47 | production_setup = production_setup or True
48 |
49 | return production_setup
50 |
51 |
52 | def execute(bench_path):
53 | """This patch checks if bench sudoers is set and regenerate supervisor and sudoers files"""
54 | user = get_config(".").get("frappe_user") or getpass.getuser()
55 |
56 | if is_sudoers_set():
57 | if is_production_set(bench_path):
58 | exec_cmd(f"sudo bench setup supervisor --yes --user {user}")
59 | service("supervisord", "restart")
60 |
61 | exec_cmd(f"sudo bench setup sudoers {user}")
62 |
--------------------------------------------------------------------------------
/bench/patches/v5/set_live_reload_config.py:
--------------------------------------------------------------------------------
1 | from bench.config.common_site_config import update_config
2 |
3 |
4 | def execute(bench_path):
5 | update_config({"live_reload": True}, bench_path)
6 |
--------------------------------------------------------------------------------
/bench/patches/v5/update_archived_sites.py:
--------------------------------------------------------------------------------
1 | """
2 | Deprecate archived_sites folder for consistency. This change is
3 | only for Frappe v14 benches. If not a v14 bench yet, skip this
4 | patch and try again later.
5 |
6 | 1. Rename folder `./archived_sites` to `./archived/sites`
7 | 2. Create a symlink `./archived_sites` => `./archived/sites`
8 |
9 | Corresponding changes in frappe/frappe via https://github.com/frappe/frappe/pull/15060
10 | """
11 | import os
12 | from pathlib import Path
13 |
14 | import click
15 | from bench.utils.app import get_current_version
16 | from semantic_version import Version
17 |
18 |
19 | def execute(bench_path):
20 | frappe_version = Version(get_current_version("frappe"))
21 |
22 | if frappe_version.major < 14 or os.name != "posix":
23 | # Returning False means patch has been skipped
24 | return False
25 |
26 | pre_patch_dir = os.getcwd()
27 | old_directory = Path(bench_path, "archived_sites")
28 | new_directory = Path(bench_path, "archived", "sites")
29 |
30 | if not old_directory.exists():
31 | return False
32 |
33 | if old_directory.is_symlink():
34 | return True
35 |
36 | os.chdir(bench_path)
37 |
38 | if not os.path.exists(new_directory):
39 | os.makedirs(new_directory)
40 |
41 | old_directory.rename(new_directory)
42 |
43 | click.secho(f"Archived sites are now stored under {new_directory}")
44 |
45 | if not os.listdir(old_directory):
46 | os.rmdir(old_directory)
47 |
48 | os.symlink(new_directory, old_directory)
49 |
50 | click.secho(f"Symlink {old_directory} that points to {new_directory}")
51 |
52 | os.chdir(pre_patch_dir)
53 |
--------------------------------------------------------------------------------
/bench/playbooks/README.md:
--------------------------------------------------------------------------------
1 | # Deploying a, developer/production-ready ERPNext website with Ansible
2 |
3 | ## Supported Platforms
4 | - Debian 8, 9
5 | - Ubuntu 14.04, 16.04
6 | - CentOS 7
7 |
8 | ## Notes for maintainers
9 | - For MariaDB playbooks refer https://github.com/PCextreme/ansible-role-mariadb
10 | - Any changes made in relation to a role should be dont inside the role and not outside it
11 |
--------------------------------------------------------------------------------
/bench/playbooks/create_user.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts: localhost
4 | become: yes
5 | become_user: root
6 | tasks:
7 | - name: Create user
8 | user:
9 | name: '{{ frappe_user }}'
10 | generate_ssh_key: yes
11 |
12 | - name: Set home folder perms
13 | file:
14 | path: '{{ user_directory }}'
15 | mode: 'o+rx'
16 | owner: '{{ frappe_user }}'
17 | group: '{{ frappe_user }}'
18 | recurse: yes
19 |
20 | - name: Set /tmp/.bench folder perms
21 | file:
22 | path: '{{ repo_path }}'
23 | owner: '{{ frappe_user }}'
24 | group: '{{ frappe_user }}'
25 | recurse: yes
26 |
27 | - name: Change default shell to bash
28 | shell: "chsh {{ frappe_user }} -s $(which bash)"
29 | ...
30 |
--------------------------------------------------------------------------------
/bench/playbooks/macosx.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: localhost
3 | become: yes
4 | become_user: root
5 |
6 | vars:
7 | bench_repo_path: "/Users/{{ ansible_user_id }}/.bench"
8 | bench_path: "/Users/{{ ansible_user_id }}/frappe-bench"
9 |
10 | tasks:
11 | - name: install prequisites
12 | homebrew:
13 | name:
14 | - cmake
15 | - redis
16 | - mariadb
17 | - nodejs
18 | state: present
19 |
20 | - name: install wkhtmltopdf
21 | homebrew_cask:
22 | name:
23 | - wkhtmltopdf
24 | state: present
25 |
26 | - name: configure mariadb
27 | include_tasks: roles/mariadb/tasks/main.yml
28 | vars:
29 | mysql_conf_tpl: roles/mariadb/files/mariadb_config.cnf
30 |
31 | - name: Install MySQLdb in global env
32 | pip: name=mysql-python version=1.2.5
33 |
34 | # setup frappe-bench
35 | - include_tasks: includes/setup_bench.yml
36 |
37 | # setup development environment
38 | - include_tasks: includes/setup_dev_env.yml
39 | when: not production
40 |
41 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/bash_screen_wall/files/screen_wall.sh:
--------------------------------------------------------------------------------
1 | if [ $TERM != 'screen' ]
2 | then
3 | PS1='HEY! USE SCREEN '$PS1
4 | fi
5 |
6 | sw() {
7 | screen -x $1 || screen -S $1
8 | }
9 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/bash_screen_wall/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Setup bash screen wall
3 | copy: src=screen_wall.sh dest=/etc/profile.d/screen_wall.sh
4 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/bench/tasks/change_ssh_port.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Change ssh port
3 | gather_facts: false
4 | hosts: localhost
5 | user: root
6 | tasks:
7 | - name: change sshd config
8 | lineinfile: >
9 | dest=/etc/ssh/sshd_config
10 | regexp="^Port"
11 | line="Port {{ ssh_port }}"
12 | state=present
13 |
14 | - name: restart ssh
15 | service: name=sshd state=reloaded
16 |
17 | - name: Change ansible ssh port to 2332
18 | set_fact:
19 | ansible_ssh_port: '{{ ssh_port }}'
20 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/bench/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check if /tmp/.bench exists
3 | stat:
4 | path: /tmp/.bench
5 | register: tmp_bench
6 |
7 | - name: Check if bench_repo_path exists
8 | stat:
9 | path: '{{ bench_repo_path }}'
10 | register: bench_repo_register
11 |
12 | - name: move /tmp/.bench if it exists
13 | command: 'cp -R /tmp/.bench {{ bench_repo_path }}'
14 | when: tmp_bench.stat.exists and not bench_repo_register.stat.exists
15 |
16 | - name: install bench
17 | pip:
18 | name: '{{ bench_repo_path }}'
19 | extra_args: '-e'
20 | become: yes
21 | become_user: root
22 |
23 | - name: Overwrite bench if required
24 | file:
25 | state: absent
26 | path: "{{ bench_path }}"
27 | when: overwrite
28 |
29 | - name: Check whether bench exists
30 | stat:
31 | path: "{{ bench_path }}"
32 | register: bench_stat
33 |
34 | - name: Fix permissions
35 | become_user: root
36 | command: chown {{ frappe_user }} -R {{ user_directory }}
37 |
38 | - name: python3 bench init for develop
39 | command: bench init {{ bench_path }} --frappe-path {{ frappe_repo_url }} --frappe-branch {{ frappe_branch }} --python {{ python }}
40 | args:
41 | creates: "{{ bench_path }}"
42 | when: not bench_stat.stat.exists and not production
43 |
44 | - name: python3 bench init for production
45 | command: bench init {{ bench_path }} --frappe-path {{ frappe_repo_url }} --frappe-branch {{ frappe_branch }} --python {{ python }}
46 | args:
47 | creates: "{{ bench_path }}"
48 | when: not bench_stat.stat.exists and production
49 |
50 | # setup common_site_config
51 | - name: setup config
52 | command: bench setup config
53 | args:
54 | creates: "{{ bench_path }}/sites/common_site_config.json"
55 | chdir: "{{ bench_path }}"
56 |
57 | - include_tasks: setup_inputrc.yml
58 |
59 | # Setup Procfile
60 | - name: Setup Procfile
61 | command: bench setup procfile
62 | args:
63 | creates: "{{ bench_path }}/Procfile"
64 | chdir: "{{ bench_path }}"
65 |
66 | # Setup Redis env for RQ
67 | - name: Setup Redis
68 | command: bench setup redis
69 | args:
70 | creates: "{{ bench_path }}/config/redis_socketio.conf"
71 | chdir: "{{ bench_path }}"
72 |
73 | # Setup an ERPNext site
74 | - include_tasks: setup_erpnext.yml
75 | when: not run_travis
76 |
77 | # Setup Bench for production environment
78 | - include_tasks: setup_bench_production.yml
79 | vars:
80 | bench_path: "{{ user_directory }}/{{ bench_name }}"
81 | when: not run_travis and production
82 | ...
83 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/bench/tasks/setup_bench_production.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Setup production
3 | become: yes
4 | become_user: root
5 | command: bench setup production {{ frappe_user }} --yes
6 | args:
7 | chdir: '{{ bench_path }}'
8 |
9 | - name: Setup Sudoers
10 | become: yes
11 | become_user: root
12 | command: bench setup sudoers {{ frappe_user }}
13 | args:
14 | chdir: '{{ bench_path }}'
15 |
16 | - name: Set correct permissions on bench.log
17 | file:
18 | path: '{{ bench_path }}/logs/bench.log'
19 | owner: '{{ frappe_user }}'
20 | group: '{{ frappe_user }}'
21 | become: yes
22 | become_user: root
23 |
24 | - name: Restart the bench
25 | command: bench restart
26 | args:
27 | chdir: '{{ bench_path }}'
28 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/bench/tasks/setup_erpnext.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check if ERPNext App exists
3 | stat: path="{{ bench_path }}/apps/erpnext"
4 | register: app
5 |
6 | - name: Get the ERPNext app
7 | command: bench get-app erpnext {{ erpnext_repo_url }} --branch {{ erpnext_branch }}
8 | args:
9 | creates: "{{ bench_path }}/apps/erpnext"
10 | chdir: "{{ bench_path }}"
11 | when: not app.stat.exists and not without_erpnext
12 |
13 | - name: Check whether the site already exists
14 | stat: path="{{ bench_path }}/sites/{{ site }}"
15 | register: site_folder
16 | when: not without_site
17 |
18 | - name: Create a new site
19 | command: "bench new-site {{ site }} --admin-password '{{ admin_password }}' --mariadb-root-password '{{ mysql_root_password }}'"
20 | args:
21 | chdir: "{{ bench_path }}"
22 | when: not without_site and not site_folder.stat.exists
23 |
24 | - name: Install ERPNext to default site
25 | command: "bench --site {{ site }} install-app erpnext"
26 | args:
27 | chdir: "{{ bench_path }}"
28 | when: not without_site and not without_erpnext
29 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/bench/tasks/setup_firewall.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Setup Firewall
3 | user: root
4 | hosts: localhost
5 |
6 | tasks:
7 | # For CentOS
8 | - name: Enable SELinux
9 | selinux: policy=targeted state=permissive
10 | when: ansible_distribution == 'CentOS'
11 |
12 | - name: Install firewalld
13 | yum: name=firewalld state=present
14 | when: ansible_distribution == 'CentOS'
15 |
16 | - name: Enable Firewall
17 | service: name=firewalld state=started enabled=yes
18 | when: ansible_distribution == 'CentOS'
19 |
20 | - name: Add firewall rules
21 | firewalld: port={{ item }}/tcp permanent=true state=enabled
22 | with_items:
23 | - 80
24 | - 443
25 | - "{{ ssh_port }}"
26 | when: ansible_distribution == 'CentOS'
27 |
28 | - name: Restart Firewall
29 | service: name=firewalld state=restarted enabled=yes
30 | when: ansible_distribution == 'CentOS'
31 |
32 | # For Ubuntu / Debian
33 | - name: Install ufw
34 | apt:
35 | state: present
36 | force: yes
37 | pkg:
38 | - python-selinux
39 | - ufw
40 | when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
41 |
42 | - name: Enable Firewall
43 | ufw: state=enabled policy=deny
44 | when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
45 |
46 | - name: Add firewall rules
47 | ufw: rule=allow proto=tcp port={{ item }}
48 | with_items:
49 | - 80
50 | - 443
51 | - "{{ ssh_port }}"
52 | when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
53 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/bench/tasks/setup_inputrc.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: insert/update inputrc for history
3 | blockinfile:
4 | dest: "{{ user_directory }}/.inputrc"
5 | create: yes
6 | block: |
7 | ## arrow up
8 | "\e[A":history-search-backward
9 | ## arrow down
10 | "\e[B":history-search-forward
11 | ...
12 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/common/tasks/debian.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Setup OpenSSL dependancy
4 | pip: name=pyOpenSSL version=16.2.0
5 |
6 | - name: install pillow prerequisites for Debian < 8
7 | apt:
8 | pkg:
9 | - libjpeg8-dev
10 | - libtiff4-dev
11 | - tcl8.5-dev
12 | - tk8.5-dev
13 | state: present
14 | when: ansible_distribution_version is version_compare('8', 'lt')
15 |
16 | - name: install pillow prerequisites for Debian 8
17 | apt:
18 | pkg:
19 | - libjpeg62-turbo-dev
20 | - libtiff5-dev
21 | - tcl8.5-dev
22 | - tk8.5-dev
23 | state: present
24 | when: ansible_distribution_version is version_compare('8', 'eq')
25 |
26 | - name: install pillow prerequisites for Debian 9
27 | apt:
28 | pkg:
29 | - libjpeg62-turbo-dev
30 | - libtiff5-dev
31 | - tcl8.5-dev
32 | - tk8.5-dev
33 | state: present
34 | when: ansible_distribution_version is version_compare('9', 'eq')
35 |
36 |
37 | - name: install pillow prerequisites for Debian >= 10
38 | apt:
39 | pkg:
40 | - libjpeg62-turbo-dev
41 | - libtiff5-dev
42 | - tcl8.6-dev
43 | - tk8.6-dev
44 | state: present
45 | when: ansible_distribution_version is version_compare('10', 'ge')
46 |
47 | - name: install pdf prerequisites debian
48 | apt:
49 | pkg:
50 | - libssl-dev
51 | state: present
52 | force: yes
53 |
54 | ...
55 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/common/tasks/debian_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install prerequisites using apt-get
4 | become: yes
5 | become_user: root
6 | apt:
7 | pkg:
8 | - dnsmasq
9 | - fontconfig
10 | - git # Version control
11 | - htop # Server stats
12 | - libcrypto++-dev
13 | - libfreetype6-dev
14 | - liblcms2-dev
15 | - libwebp-dev
16 | - libxext6
17 | - libxrender1
18 | - libxslt1-dev
19 | - libxslt1.1
20 | - libffi-dev
21 | - ntp # Clock synchronization
22 | - postfix # Mail Server
23 | - python3-dev # Installing python developer suite
24 | - python-tk
25 | - screen # To aid ssh sessions with connectivity problems
26 | - vim # Is that supposed to be a question!?
27 | - xfonts-75dpi
28 | - xfonts-base
29 | - zlib1g-dev
30 | - apt-transport-https
31 | - libsasl2-dev
32 | - libldap2-dev
33 | - libcups2-dev
34 | - pv # Show progress during database restore
35 | state: present
36 | force: yes
37 |
38 | - include_tasks: debian.yml
39 | when: ansible_distribution == 'Debian'
40 |
41 | - include_tasks: ubuntu.yml
42 | when: ansible_distribution == 'Ubuntu'
43 |
44 | ...
45 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/common/tasks/macos.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - hosts: localhost
4 | become: yes
5 | become_user: root
6 | vars:
7 | bench_repo_path: "/Users/{{ ansible_user_id }}/.bench"
8 | bench_path: "/Users/{{ ansible_user_id }}/frappe-bench"
9 | tasks:
10 | # install pre-requisites
11 | - name: install prequisites
12 | homebrew:
13 | name:
14 | - cmake
15 | - redis
16 | - mariadb
17 | - nodejs
18 | state: present
19 |
20 | # install wkhtmltopdf
21 | - name: cask installs
22 | homebrew_cask:
23 | name:
24 | - wkhtmltopdf
25 | state: present
26 |
27 | - name: configure mariadb
28 | include_tasks: roles/mariadb/tasks/main.yml
29 | vars:
30 | mysql_conf_tpl: roles/mariadb/files/mariadb_config.cnf
31 |
32 | # setup frappe-bench
33 | - include_tasks: includes/setup_bench.yml
34 |
35 | # setup development environment
36 | - include_tasks: includes/setup_dev_env.yml
37 | when: not production
38 |
39 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/common/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Install's prerequisites, like fonts, image libraries, vim, screen, python3-dev
3 |
4 | - include_tasks: debian_family.yml
5 | when: ansible_os_family == 'Debian'
6 |
7 | - include_tasks: redhat_family.yml
8 | when: ansible_os_family == "RedHat"
9 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/common/tasks/redhat_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install IUS repo for python 3.6
4 | become: yes
5 | become_user: root
6 | yum:
7 | name: https://repo.ius.io/ius-release-el7.rpm
8 | state: present
9 |
10 | - name: "Setup prerequisites using yum"
11 | become: yes
12 | become_user: root
13 | yum:
14 | name:
15 | - bzip2-devel
16 | - cronie
17 | - dnsmasq
18 | - freetype-devel
19 | - git
20 | - htop
21 | - lcms2-devel
22 | - libjpeg-devel
23 | - libtiff-devel
24 | - libffi-devel
25 | - libwebp-devel
26 | - libXext
27 | - libXrender
28 | - libzip-devel
29 | - libffi-devel
30 | - ntp
31 | - openssl-devel
32 | - postfix
33 | - python36u
34 | - python-devel
35 | - python-setuptools
36 | - python-pip
37 | - redis
38 | - screen
39 | - sudo
40 | - tcl-devel
41 | - tk-devel
42 | - vim
43 | - which
44 | - xorg-x11-fonts-75dpi
45 | - xorg-x11-fonts-Type1
46 | - zlib-devel
47 | - openssl-devel
48 | - openldap-devel
49 | - libselinux-python
50 | - cups-libs
51 | state: present
52 | ...
53 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/common/tasks/ubuntu.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: install pillow prerequisites for Ubuntu < 14.04
4 | apt:
5 | pkg:
6 | - libjpeg8-dev
7 | - libtiff4-dev
8 | - tcl8.5-dev
9 | - tk8.5-dev
10 | state: present
11 | force: yes
12 | when: ansible_distribution_version is version_compare('14.04', 'lt')
13 |
14 | - name: install pillow prerequisites for Ubuntu >= 14.04
15 | apt:
16 | pkg:
17 | - libjpeg8-dev
18 | - libtiff5-dev
19 | - tcl8.6-dev
20 | - tk8.6-dev
21 | state: present
22 | force: yes
23 | when: ansible_distribution_version is version_compare('14.04', 'ge')
24 |
25 | - name: install pdf prerequisites for Ubuntu < 18.04
26 | apt:
27 | pkg:
28 | - libssl-dev
29 | state: present
30 | force: yes
31 | when: ansible_distribution_version is version_compare('18.04', 'lt')
32 |
33 | - name: install pdf prerequisites for Ubuntu >= 18.04
34 | apt:
35 | pkg:
36 | - libssl1.1
37 | state: present
38 | force: yes
39 | when: ansible_distribution_version is version_compare('18.04', 'ge')
40 |
41 | ...
42 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/dns_caching/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: restart network manager
3 | service: name=NetworkManager state=restarted
4 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/dns_caching/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check NetworkManager.conf exists
3 | stat:
4 | path: /etc/NetworkManager/NetworkManager.conf
5 | register: result
6 |
7 | - name: Unmask NetworkManager service
8 | command: systemctl unmask NetworkManager
9 | when: result.stat.exists
10 |
11 | - name: Add dnsmasq to network config
12 | lineinfile: >
13 | dest=/etc/NetworkManager/NetworkManager.conf
14 | regexp="dns="
15 | line="dns=dnsmasq"
16 | state=present
17 | when: result.stat.exists
18 | notify:
19 | - restart network manager
20 | ...
21 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/fail2ban/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | fail2ban_nginx_access_log: /var/log/nginx/*access.log
3 | maxretry: 6
4 | bantime: 600
5 | findtime: 600
6 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/fail2ban/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: restart fail2ban
3 | service: name=fail2ban state=restarted
--------------------------------------------------------------------------------
/bench/playbooks/roles/fail2ban/tasks/configure_nginx_jail.yml:
--------------------------------------------------------------------------------
1 | - name: Configure fail2ban jail options
2 | hosts: localhost
3 | become: yes
4 | become_user: root
5 | vars_files:
6 | - ../defaults/main.yml
7 | tasks:
8 |
9 | - name: Setup filter
10 | template: src="../templates/nginx-proxy-filter.conf.j2" dest="/etc/fail2ban/filter.d/nginx-proxy.conf"
11 | - name: Setup jail
12 | template: src="../templates/nginx-proxy-jail.conf.j2" dest="/etc/fail2ban/jail.d/nginx-proxy.conf"
13 | - name: restart service
14 | service: name=fail2ban state=restarted
15 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/fail2ban/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install fail2ban
3 | yum: name=fail2ban state=present
4 | when: ansible_distribution == 'CentOS'
5 |
6 | - name: Install fail2ban
7 | apt: name=fail2ban state=present
8 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
9 |
10 | - name: Enable fail2ban
11 | service: name=fail2ban enabled=yes
12 |
13 | - name: Create jail.d
14 | file: path=/etc/fail2ban/jail.d state=directory
15 |
16 | - name: Setup filters
17 | template: src="{{item}}-filter.conf.j2" dest="/etc/fail2ban/filter.d/{{item}}.conf"
18 | with_items:
19 | - nginx-proxy
20 | notify:
21 | - restart fail2ban
22 |
23 | - name: setup jails
24 | template: src="{{item}}-jail.conf.j2" dest="/etc/fail2ban/jail.d/{{item}}.conf"
25 | with_items:
26 | - nginx-proxy
27 | notify:
28 | - restart fail2ban
29 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/fail2ban/templates/nginx-proxy-filter.conf.j2:
--------------------------------------------------------------------------------
1 | # Block IPs trying to use server as proxy.
2 | [Definition]
3 | failregex = .*\" 400
4 | .*"[A-Z]* /(cms|muieblackcat|db|cpcommerce|cgi-bin|wp-login|joomla|awstatstotals|wp-content|wp-includes|pma|phpmyadmin|myadmin|mysql|mysqladmin|sqladmin|mypma|admin|xampp|mysqldb|pmadb|phpmyadmin1|phpmyadmin2).*" 4[\d][\d]
5 | .*".*supports_implicit_sdk_logging.*" 4[\d][\d]
6 | .*".*activities?advertiser_tracking_enabled.*" 4[\d][\d]
7 | .*".*/picture?type=normal.*" 4[\d][\d]
8 | .*".*/announce.php?info_hash=.*" 4[\d][\d]
9 |
10 | ignoreregex =
--------------------------------------------------------------------------------
/bench/playbooks/roles/fail2ban/templates/nginx-proxy-jail.conf.j2:
--------------------------------------------------------------------------------
1 | ## block hosts trying to abuse our server as a forward proxy
2 | [nginx-proxy]
3 | enabled = true
4 | filter = nginx-proxy
5 | logpath = {{ fail2ban_nginx_access_log }}
6 | action = iptables-multiport[name=NoNginxProxy, port="http,https"]
7 | maxretry = {{ maxretry }}
8 | bantime = {{ bantime }}
9 | findtime = {{ findtime }}
--------------------------------------------------------------------------------
/bench/playbooks/roles/frappe_selinux/files/frappe_selinux.te:
--------------------------------------------------------------------------------
1 | module frappe_selinux 1.0;
2 |
3 | require {
4 | type user_home_dir_t;
5 | type httpd_t;
6 | type user_home_t;
7 | type soundd_port_t;
8 | class tcp_socket name_connect;
9 | class lnk_file read;
10 | class dir { getattr search };
11 | class file { read open };
12 | }
13 |
14 | #============= httpd_t ==============
15 |
16 | #!!!! This avc is allowed in the current policy
17 | allow httpd_t soundd_port_t:tcp_socket name_connect;
18 |
19 | #!!!! This avc is allowed in the current policy
20 | allow httpd_t user_home_dir_t:dir search;
21 |
22 | #!!!! This avc is allowed in the current policy
23 | allow httpd_t user_home_t:dir { getattr search };
24 |
25 | #!!!! This avc can be allowed using the boolean 'httpd_read_user_content'
26 | allow httpd_t user_home_t:file open;
27 |
28 | #!!!! This avc is allowed in the current policy
29 | allow httpd_t user_home_t:file read;
30 |
31 | #!!!! This avc is allowed in the current policy
32 | allow httpd_t user_home_t:lnk_file read;
--------------------------------------------------------------------------------
/bench/playbooks/roles/frappe_selinux/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install deps
3 | yum:
4 | name:
5 | - policycoreutils-python
6 | - selinux-policy-devel
7 | state: present
8 | when: ansible_distribution == 'CentOS'
9 |
10 | - name: Check enabled SELinux modules
11 | shell: semanage module -l
12 | register: enabled_modules
13 | when: ansible_distribution == 'CentOS'
14 |
15 | - name: Copy frappe_selinux policy
16 | copy: src=frappe_selinux.te dest=/root/frappe_selinux.te
17 | register: dest_frappe_selinux_te
18 | when: ansible_distribution == 'CentOS'
19 |
20 | - name: Compile frappe_selinux policy
21 | shell: "make -f /usr/share/selinux/devel/Makefile frappe_selinux.pp && semodule -i frappe_selinux.pp"
22 | args:
23 | chdir: /root/
24 | when: "ansible_distribution == 'CentOS' and enabled_modules.stdout.find('frappe_selinux') == -1 or dest_frappe_selinux_te.changed"
25 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/locale/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | locale_keymap: us
3 | locale_lang: en_US.utf8
4 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/locale/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check current locale
3 | shell: localectl
4 | register: locale_test
5 | when: ansible_distribution == 'Centos' or ansible_distribution == 'Ubuntu'
6 |
7 | - name: Set Locale
8 | command: "localectl set-locale LANG={{ locale_lang }}"
9 | when: (ansible_distribution == 'Centos' or ansible_distribution == 'Ubuntu') and locale_test.stdout.find('LANG=locale_lang') == -1
10 |
11 | - name: Set keymap
12 | command: "localectl set-keymap {{ locale_keymap }}"
13 | when: "(ansible_distribution == 'Centos' or ansible_distribution == 'Ubuntu') and locale_test.stdout.find('Keymap:locale_keymap') == -1"
14 |
15 | - name: Set Locale as en_US
16 | lineinfile: dest=/etc/environment backup=yes line="{{ item }}"
17 | with_items:
18 | - "LC_ALL=en_US.UTF-8"
19 | - "LC_CTYPE=en_US.UTF-8"
20 | - "LANG=en_US.UTF-8"
21 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/logwatch/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | logwatch_emails: "{{ admin_emails }}"
3 | logwatch_detail: High
4 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/logwatch/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install logwatch
3 | yum: name=logwatch state=present
4 | when: ansible_distribution == 'CentOS'
5 |
6 | - name: Install logwatch on Ubuntu or Debian
7 | apt: name=logwatch state=present
8 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
9 |
10 | - name: Copy logwatch config
11 | template: src=logwatch.conf.j2 dest=/etc/logwatch/conf/logwatch.conf backup=yes
12 | when: admin_emails is defined
13 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/logwatch/templates/logwatch.conf.j2:
--------------------------------------------------------------------------------
1 | MailTo = {{ logwatch_emails }}
2 | Detail = {{ logwatch_detail }}
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/README.md:
--------------------------------------------------------------------------------
1 | # Ansible Role: MariaDB
2 |
3 | Installs MariaDB
4 |
5 | ## Supported platforms
6 |
7 | ```
8 | CentOS 6 & 7
9 | Ubuntu 14.04
10 | Ubuntu 16.04
11 | Debain 9
12 | ```
13 |
14 | ## Post install
15 |
16 | Run `mariadb-secure-installation`
17 |
18 | ## Requirements
19 |
20 | None
21 |
22 | ## Role Variables
23 |
24 | MariaDB version:
25 |
26 | ```
27 | mariadb_version: 10.2
28 | ```
29 |
30 | Configuration template:
31 |
32 | ```
33 | mysql_conf_tpl: change_me
34 | ```
35 |
36 | Configuration filename:
37 |
38 | ```
39 | mysql_conf_file: settings.cnf
40 | ```
41 |
42 | ### Experimental unattended mariadb-secure-installation
43 |
44 | ```
45 | ansible-playbook release.yml --extra-vars "mysql_secure_installation=true mysql_root_password=your_very_secret_password"
46 | ```
47 |
48 | ## Dependencies
49 |
50 | None
51 |
52 | ## Example Playbook
53 |
54 | ```
55 | - hosts: servers
56 | roles:
57 | - { role: mariadb }
58 | ```
59 |
60 | ## Credits
61 |
62 | - [Attila van der Velde](https://github.com/vdvm)
63 |
64 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | mysql_conf_tpl: change_me
3 | mysql_conf_file: settings.cnf
4 |
5 | mysql_secure_installation: false
6 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/files/debian_mariadb_config.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 | innodb-file-format=barracuda
3 | innodb-file-per-table=1
4 | innodb-large-prefix=1
5 | character-set-client-handshake = FALSE
6 | character-set-server = utf8mb4
7 | collation-server = utf8mb4_unicode_ci
8 | max_allowed_packet=256M
9 |
10 | [mysql]
11 | default-character-set = utf8mb4
12 |
13 | [mysqldump]
14 | max_allowed_packet=256M
15 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/files/mariadb_config.cnf:
--------------------------------------------------------------------------------
1 | [mysqld]
2 |
3 | # GENERAL #
4 | user = mysql
5 | default-storage-engine = InnoDB
6 | socket = /var/lib/mysql/mysql.sock
7 | pid-file = /var/lib/mysql/mysql.pid
8 |
9 | # MyISAM #
10 | key-buffer-size = 32M
11 | myisam-recover = FORCE,BACKUP
12 |
13 | # SAFETY #
14 | max-allowed-packet = 256M
15 | max-connect-errors = 1000000
16 | innodb = FORCE
17 |
18 | # DATA STORAGE #
19 | datadir = /var/lib/mysql/
20 |
21 | # BINARY LOGGING #
22 | log-bin = /var/lib/mysql/mysql-bin
23 | expire-logs-days = 14
24 | sync-binlog = 1
25 |
26 | # REPLICATION #
27 | server-id = 1
28 |
29 | # CACHES AND LIMITS #
30 | tmp-table-size = 32M
31 | max-heap-table-size = 32M
32 | query-cache-type = 0
33 | query-cache-size = 0
34 | max-connections = 500
35 | thread-cache-size = 50
36 | open-files-limit = 65535
37 | table-definition-cache = 4096
38 | table-open-cache = 10240
39 |
40 | # INNODB #
41 | innodb-flush-method = O_DIRECT
42 | innodb-log-files-in-group = 2
43 | innodb-log-file-size = 512M
44 | innodb-flush-log-at-trx-commit = 1
45 | innodb-file-per-table = 1
46 | innodb-buffer-pool-size = {{ (ansible_memtotal_mb*0.685)|round|int }}M
47 | innodb-file-format = barracuda
48 | innodb-large-prefix = 1
49 | collation-server = utf8mb4_unicode_ci
50 | character-set-server = utf8mb4
51 | character-set-client-handshake = FALSE
52 | max_allowed_packet = 256M
53 |
54 | # LOGGING #
55 | log-error = /var/lib/mysql/mysql-error.log
56 | log-queries-not-using-indexes = 0
57 | slow-query-log = 1
58 | slow-query-log-file = /var/lib/mysql/mysql-slow.log
59 |
60 | [mysql]
61 | default-character-set = utf8mb4
62 |
63 | [mysqldump]
64 | max_allowed_packet=256M
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: restart mariadb
3 | service: name=mariadb state=restarted
4 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/tasks/centos.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add repo file
3 | template: src=mariadb_centos.repo.j2 dest=/etc/yum.repos.d/mariadb.repo owner=root group=root mode=0644
4 |
5 | - name: Install MariaDB
6 | yum:
7 | name:
8 | - MariaDB-server
9 | - MariaDB-client
10 | enablerepo: mariadb
11 | state: present
12 |
13 | - name: Install MySQLdb Python package for secure installations.
14 | yum:
15 | name:
16 | - MySQL-python
17 | state: present
18 | when: mysql_secure_installation and mysql_root_password is defined
19 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/tasks/debian.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add apt key for mariadb for Debian <= 8
3 | apt_key: keyserver=hkp://keyserver.ubuntu.com:80 id=0xcbcb082a1bb943db state=present
4 | when: ansible_distribution_major_version is version_compare('8', 'le')
5 |
6 | - name: Install dirmngr for apt key for mariadb for Debian > 8
7 | apt:
8 | pkg: dirmngr
9 | state: present
10 | when: ansible_distribution_major_version is version_compare('8', 'gt')
11 |
12 | - name: Add apt key for mariadb for Debian > 8
13 | apt_key: keyserver=hkp://keyserver.ubuntu.com:80 id=0xF1656F24C74CD1D8 state=present
14 | when: ansible_distribution_major_version is version_compare('8', 'gt')
15 |
16 | - name: Add apt repository
17 | apt_repository:
18 | repo: 'deb [arch=amd64,i386] http://ams2.mirrors.digitalocean.com/mariadb/repo/{{ mariadb_version }}/debian {{ ansible_distribution_release }} main'
19 | state: present
20 |
21 | - name: Add apt repository
22 | apt_repository:
23 | repo: 'deb-src [arch=amd64,i386] http://ams2.mirrors.digitalocean.com/mariadb/repo/{{ mariadb_version }}/debian {{ ansible_distribution_release }} main'
24 | state: present
25 |
26 | - name: Unattended package installation
27 | shell: export DEBIAN_FRONTEND=noninteractive
28 |
29 | - name: apt-get install
30 | apt:
31 | pkg:
32 | - mariadb-server
33 | - mariadb-client
34 | - mariadb-common
35 | - libmariadbclient18
36 | - python3-mysqldb
37 | update_cache: yes
38 | state: present
39 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - include_tasks: centos.yml
3 | when: ansible_distribution == 'CentOS' and ansible_distribution_major_version|int >= 6
4 |
5 | - include_tasks: ubuntu-trusty.yml
6 | when: ansible_distribution == 'Ubuntu' and ansible_distribution_version == '14.04'
7 |
8 | - include_tasks: ubuntu-xenial_bionic.yml
9 | when: ansible_distribution == 'Ubuntu' and ansible_distribution_major_version|int >= 16
10 |
11 | - name: Add configuration
12 | template:
13 | src: '{{ mysql_conf_tpl }}'
14 | dest: '{{ mysql_conf_dir[ansible_distribution] }}/{{ mysql_conf_file }}'
15 | owner: root
16 | group: root
17 | mode: 0644
18 | when: mysql_conf_tpl != 'change_me' and ansible_distribution != 'Debian'
19 | notify: restart mariadb
20 |
21 | - include_tasks: debian.yml
22 | when: ansible_distribution == 'Debian'
23 |
24 | - name: Add configuration
25 | template:
26 | src: '{{ mysql_conf_tpl }}'
27 | dest: '{{ mysql_conf_dir[ansible_distribution] }}/{{ mysql_conf_file }}'
28 | owner: root
29 | group: root
30 | mode: 0644
31 | when: mysql_conf_tpl != 'change_me' and ansible_distribution == 'Debian'
32 | notify: restart mariadb
33 |
34 | - name: Add additional conf for MariaDB 10.2 in mariadb.conf.d
35 | blockinfile:
36 | path: /etc/mysql/conf.d/settings.cnf
37 | block: |
38 | # Import all .cnf files from configuration directory
39 | !includedir /etc/mysql/mariadb.conf.d/
40 | become: yes
41 | become_user: root
42 | when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
43 |
44 | - name: Add additional conf for MariaDB 10.2 in mariadb.conf.d
45 | blockinfile:
46 | path: /etc/mysql/mariadb.conf.d/erpnext.cnf
47 | block: |
48 | [mysqld]
49 | pid-file = /var/run/mysqld/mysqld.pid
50 | socket = /var/run/mysqld/mysqld.sock
51 |
52 | # setting appeared inside mysql but overwritten by mariadb inside mariadb.conf.d/xx-server.cnf valued as utf8mb4_general_ci
53 |
54 | collation-server = utf8mb4_unicode_ci
55 | create: yes
56 | become: yes
57 | become_user: root
58 | when: ansible_distribution == 'Ubuntu' or ansible_distribution == 'Debian'
59 |
60 | - name: Start and enable service
61 | service:
62 | name: mariadb
63 | state: started
64 | enabled: yes
65 |
66 | - debug:
67 | msg: "{{ mysql_root_password }}"
68 |
69 | - include_tasks: mysql_secure_installation.yml
70 | when: mysql_root_password is defined
71 |
72 | - debug:
73 | var: mysql_secure_installation
74 | when: mysql_secure_installation and mysql_root_password is defined
75 |
76 | ...
77 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/tasks/mysql_secure_installation.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - debug:
4 | msg: "{{ mysql_root_password }}"
5 |
6 | # create root .my.cnf config file
7 | - name: Add .my.cnf
8 | template: src=my.cnf.j2 dest=/root/.my.cnf owner=root group=root mode=0600
9 |
10 | # Set root password
11 | # UPDATE mysql.user SET Password=PASSWORD('mysecret') WHERE User='root';
12 | # FLUSH PRIVILEGES;
13 |
14 | - name: Set root Password
15 | mysql_user: login_password={{ mysql_root_password }} check_implicit_admin=yes name=root host={{ item }} password={{ mysql_root_password }} state=present
16 | with_items:
17 | - localhost
18 | - 127.0.0.1
19 | - ::1
20 |
21 | - name: Reload privilege tables
22 | command: 'mariadb -ne "{{ item }}"'
23 | with_items:
24 | - FLUSH PRIVILEGES
25 | changed_when: False
26 | when: run_travis is not defined
27 |
28 | - name: Remove anonymous users
29 | command: 'mariadb -ne "{{ item }}"'
30 | with_items:
31 | - DELETE FROM mysql.user WHERE User=''
32 | changed_when: False
33 | when: run_travis is not defined
34 |
35 | - name: Disallow root login remotely
36 | command: 'mariadb -ne "{{ item }}"'
37 | with_items:
38 | - DELETE FROM mysql.user WHERE User='root' AND Host NOT IN ('localhost', '127.0.0.1', '::1')
39 | changed_when: False
40 | when: run_travis is not defined
41 |
42 | - name: Remove test database and access to it
43 | command: 'mariadb -ne "{{ item }}"'
44 | with_items:
45 | - DROP DATABASE IF EXISTS test
46 | - DELETE FROM mysql.db WHERE Db='test' OR Db='test\\_%'
47 | changed_when: False
48 | when: run_travis is not defined
49 |
50 | - name: Reload privilege tables
51 | command: 'mariadb -ne "{{ item }}"'
52 | with_items:
53 | - FLUSH PRIVILEGES
54 | changed_when: False
55 | when: run_travis is not defined
56 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/tasks/ubuntu-trusty.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add repo file
3 | template: src=mariadb_ubuntu.list.j2 dest=/etc/apt/sources.list.d/mariadb.list owner=root group=root mode=0644
4 | register: mariadb_list
5 |
6 | - name: Add repo key
7 | apt_key: id=1BB943DB url=http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xCBCB082A1BB943DB state=present
8 | register: mariadb_key
9 |
10 | - name: Update apt cache
11 | apt: update_cache=yes
12 | when: mariadb_list.changed == True or mariadb_key.changed == True
13 |
14 | - name: Unattended package installation
15 | shell: export DEBIAN_FRONTEND=noninteractive
16 | changed_when: false
17 |
18 | - name: Install MariaDB
19 | apt:
20 | pkg:
21 | - mariadb-server
22 | - mariadb-client
23 | - libmariadbclient18
24 | state: present
25 |
26 | - name: Install MySQLdb Python package for secure installations.
27 | apt:
28 | pkg:
29 | - python3-mysqldb
30 | state: present
31 | when: mysql_secure_installation and mysql_root_password is defined
32 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/tasks/ubuntu-xenial_bionic.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add repo file
3 | template: src=mariadb_ubuntu.list.j2 dest=/etc/apt/sources.list.d/mariadb.list owner=root group=root mode=0644
4 | register: mariadb_list
5 |
6 | - name: Add repo key
7 | apt_key: id=C74CD1D8 url=http://keyserver.ubuntu.com/pks/lookup?op=get&search=0xF1656F24C74CD1D8 state=present
8 | register: mariadb_key
9 |
10 | - name: Update apt cache
11 | apt: update_cache=yes
12 | when: mariadb_list.changed == True or mariadb_key.changed == True
13 |
14 | - name: Unattended package installation
15 | shell: export DEBIAN_FRONTEND=noninteractive
16 | changed_when: false
17 |
18 | - name: Install MariaDB
19 | apt:
20 | pkg:
21 | - mariadb-server
22 | - mariadb-client
23 | - libmariadbclient18
24 | state: present
25 |
26 | - name: Install MySQLdb Python package for secure installations.
27 | apt:
28 | pkg:
29 | - python3-mysqldb
30 | state: present
31 | when: mysql_secure_installation and mysql_root_password is defined
32 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/templates/mariadb_centos.repo.j2:
--------------------------------------------------------------------------------
1 | # MariaDB CentOS {{ ansible_distribution_major_version|int }} repository list
2 | # http://mariadb.org/mariadb/repositories/
3 | [mariadb]
4 | name = MariaDB
5 | baseurl = http://yum.mariadb.org/{{ mariadb_version }}/centos{{ ansible_distribution_major_version|int }}-amd64
6 | gpgkey=https://yum.mariadb.org/RPM-GPG-KEY-MariaDB
7 | gpgcheck=1
8 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/templates/mariadb_debian.list.j2:
--------------------------------------------------------------------------------
1 | # MariaDB {{ mariadb_version }} Debian {{ ansible_distribution_release | title }} repository list
2 | # http://mariadb.org/mariadb/repositories/
3 | deb http://ams2.mirrors.digitalocean.com/mariadb/repo/{{ mariadb_version }}/debian {{ ansible_distribution_release | lower }} main
4 | deb-src http://ams2.mirrors.digitalocean.com/mariadb/repo/{{ mariadb_version }}/debian {{ ansible_distribution_release | lower }} main
5 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/templates/mariadb_ubuntu.list.j2:
--------------------------------------------------------------------------------
1 | # MariaDB Ubuntu {{ ansible_distribution_release | title }} repository list
2 | # http://mariadb.org/mariadb/repositories/
3 | deb http://ams2.mirrors.digitalocean.com/mariadb/repo/{{ mariadb_version }}/ubuntu {{ ansible_distribution_release | lower }} main
4 | deb-src http://ams2.mirrors.digitalocean.com/mariadb/repo/{{ mariadb_version }}/ubuntu {{ ansible_distribution_release | lower }} main
5 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/templates/my.cnf.j2:
--------------------------------------------------------------------------------
1 | [client]
2 | user=root
3 | password={{ mysql_root_password }}
4 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/mariadb/vars/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | mysql_conf_dir:
3 | "CentOS": /etc/my.cnf.d
4 | "Ubuntu": /etc/mysql/conf.d
5 | "Debian": /etc/mysql/conf.d
6 | mysql_conf_tpl: files/mariadb_config.cnf
7 | mysql_secure_installation: True
8 | ...
9 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/README.md:
--------------------------------------------------------------------------------
1 | # Ansible Role: Nginx
2 |
3 | [](https://travis-ci.org/geerlingguy/ansible-role-nginx)
4 |
5 | Installs Nginx on RedHat/CentOS or Debian/Ubuntu linux servers.
6 |
7 | This role installs and configures the latest version of Nginx from the Nginx yum repository (on RedHat-based systems) or via apt (on Debian-based systems). You will likely need to do extra setup work after this role has installed Nginx, like adding your own [virtualhost].conf file inside `/etc/nginx/conf.d/`, describing the location and options to use for your particular website.
8 |
9 | ## Requirements
10 |
11 | None.
12 |
13 | ## Role Variables
14 |
15 | Available variables are listed below, along with default values (see `defaults/main.yml`):
16 |
17 | nginx_vhosts: []
18 |
19 | A list of vhost definitions (server blocks) for Nginx virtual hosts. If left empty, you will need to supply your own virtual host configuration. See the commented example in `defaults/main.yml` for available server options. If you have a large number of customizations required for your server definition(s), you're likely better off managing the vhost configuration file yourself, leaving this variable set to `[]`.
20 |
21 | nginx_remove_default_vhost: false
22 |
23 | Whether to remove the 'default' virtualhost configuration supplied by Nginx. Useful if you want the base `/` URL to be directed at one of your own virtual hosts configured in a separate .conf file.
24 |
25 | nginx_upstreams: []
26 |
27 | If you are configuring Nginx as a load balancer, you can define one or more upstream sets using this variable. In addition to defining at least one upstream, you would need to configure one of your server blocks to proxy requests through the defined upstream (e.g. `proxy_pass http://myapp1;`). See the commented example in `defaults/main.yml` for more information.
28 |
29 | nginx_user: "nginx"
30 |
31 | The user under which Nginx will run. Defaults to `nginx` for RedHat, and `www-data` for Debian.
32 |
33 | nginx_worker_processes: "1"
34 | nginx_worker_connections: "1024"
35 |
36 | `nginx_worker_processes` should be set to the number of cores present on your machine. Connections (find this number with `grep processor /proc/cpuinfo | wc -l`). `nginx_worker_connections` is the number of connections per process. Set this higher to handle more simultaneous connections (and remember that a connection will be used for as long as the keepalive timeout duration for every client!).
37 |
38 | nginx_error_log: "/var/log/nginx/error.log warn"
39 | nginx_access_log: "/var/log/nginx/access.log main buffer=16k"
40 |
41 | Configuration of the default error and access logs. Set to `off` to disable a log entirely.
42 |
43 | nginx_sendfile: "on"
44 | nginx_tcp_nopush: "on"
45 | nginx_tcp_nodelay: "on"
46 |
47 | TCP connection options. See [this blog post](https://t37.net/nginx-optimization-understanding-sendfile-tcp_nodelay-and-tcp_nopush.html) for more information on these directives.
48 |
49 | nginx_keepalive_timeout: "65"
50 | nginx_keepalive_requests: "100"
51 |
52 | Nginx keepalive settings. Timeout should be set higher (10s+) if you have more polling-style traffic (AJAX-powered sites especially), or lower (<10s) if you have a site where most users visit a few pages and don't send any further requests.
53 |
54 | nginx_client_max_body_size: "64m"
55 |
56 | This value determines the largest file upload possible, as uploads are passed through Nginx before hitting a backend like `php-fpm`. If you get an error like `client intended to send too large body`, it means this value is set too low.
57 |
58 | nginx_proxy_cache_path: ""
59 |
60 | Set as the `proxy_cache_path` directive in the `nginx.conf` file. By default, this will not be configured (if left as an empty string), but if you wish to use Nginx as a reverse proxy, you can set this to a valid value (e.g. `"/var/cache/nginx keys_zone=cache:32m"`) to use Nginx's cache (further proxy configuration can be done in individual server configurations).
61 |
62 | nginx_default_release: ""
63 |
64 | (For Debian/Ubuntu only) Allows you to set a different repository for the installation of Nginx. As an example, if you are running Debian's wheezy release, and want to get a newer version of Nginx, you can install the `wheezy-backports` repository and set that value here, and Ansible will use that as the `-t` option while installing Nginx.
65 |
66 | ## Dependencies
67 |
68 | None.
69 |
70 | ## Example Playbook
71 |
72 | - hosts: server
73 | roles:
74 | - { role: geerlingguy.nginx }
75 |
76 | ## License
77 |
78 | MIT / BSD
79 |
80 | ## Author Information
81 |
82 | This role was created in 2014 by [Jeff Geerling](http://jeffgeerling.com/), author of [Ansible for DevOps](http://ansiblefordevops.com/).
83 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Used only for Debian/Ubuntu installation, as the -t option for apt.
3 | nginx_default_release: ""
4 |
5 | nginx_worker_processes: "1"
6 | nginx_worker_connections: "1024"
7 |
8 | nginx_error_log: "/var/log/nginx/error.log warn"
9 | nginx_access_log: "/var/log/nginx/access.log main buffer=16k"
10 |
11 | nginx_sendfile: "on"
12 | nginx_tcp_nopush: "on"
13 | nginx_tcp_nodelay: "on"
14 |
15 | nginx_keepalive_timeout: "65"
16 | nginx_keepalive_requests: "100"
17 |
18 | nginx_client_max_body_size: "64m"
19 |
20 | nginx_proxy_cache_path: ""
21 |
22 | nginx_remove_default_vhost: false
23 | nginx_vhosts: []
24 | # Example vhost below, showing all available options:
25 | # - {
26 | # listen: "80 default_server", # default: "80 default_server"
27 | # server_name: "example.com", # default: N/A
28 | # root: "/var/www/example.com", # default: N/A
29 | # index: "index.html index.htm", # default: "index.html index.htm"
30 | #
31 | # # Properties that are only added if defined:
32 | # error_page: "",
33 | # access_log: "",
34 | # extra_config: "" # Can be used to add extra config blocks (multiline).
35 | # }
36 |
37 | nginx_upstreams: []
38 | # - {
39 | # name: myapp1,
40 | # strategy: "ip_hash", # "least_conn", etc.
41 | # servers: {
42 | # "srv1.example.com",
43 | # "srv2.example.com weight=3",
44 | # "srv3.example.com"
45 | # }
46 | # }
47 | nginx_conf_file: nginx.conf.j2
48 | setup_www_redirect: false
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/handlers/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: restart nginx
3 | service: name=nginx state=restarted
4 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/meta/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | dependencies: []
3 |
4 | galaxy_info:
5 | author: geerlingguy
6 | description: Nginx installation for Linux/UNIX.
7 | company: "Midwestern Mac, LLC"
8 | license: "license (BSD, MIT)"
9 | min_ansible_version: 1.4
10 | platforms:
11 | - name: EL
12 | versions:
13 | - 6
14 | - 7
15 | - name: Debian
16 | versions:
17 | - all
18 | - name: Ubuntu
19 | versions:
20 | - all
21 | categories:
22 | - development
23 | - web
24 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # Variable setup.
3 | - name: Include OS-specific variables.
4 | include_vars: "{{ ansible_os_family }}.yml"
5 |
6 | - name: Define nginx_user.
7 | set_fact:
8 | nginx_user: "{{ __nginx_user }}"
9 | when: nginx_user is not defined
10 |
11 | # Setup/install tasks.
12 | - include_tasks: setup-RedHat.yml
13 | when: ansible_os_family == 'RedHat'
14 |
15 | - include_tasks: setup-Debian.yml
16 | when: ansible_os_family == 'Debian'
17 |
18 | # Replace default nginx config with nginx template
19 | - name: Rename default nginx.conf to nginx.conf.old
20 | command: mv /etc/nginx/nginx.conf /etc/nginx/nginx.conf.old
21 | when: ansible_os_family == 'Debian'
22 |
23 | # Nginx setup.
24 | - name: Copy nginx configuration in place.
25 | template:
26 | src: "{{ nginx_conf_file }}"
27 | dest: /etc/nginx/nginx.conf
28 | owner: root
29 | group: root
30 | mode: 0644
31 | notify: restart nginx
32 |
33 | - name: Setup www redirect
34 | template:
35 | src: ../files/www_redirect.conf
36 | dest: /etc/nginx/conf.d/
37 | owner: root
38 | group: root
39 | mode: 0644
40 | notify: restart nginx
41 | when: setup_www_redirect
42 |
43 | - name: Enable SELinux
44 | selinux: policy=targeted state=permissive
45 | when: ansible_distribution == 'CentOS'
46 |
47 | - name: Ensure nginx is started and enabled to start at boot.
48 | service: name=nginx state=started enabled=yes
49 |
50 | - include_tasks: vhosts.yml
51 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/tasks/setup-Debian.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Add nginx apt repository key for Debian < 8
3 | apt_key:
4 | url: http://nginx.org/keys/nginx_signing.key
5 | state: present
6 | when: ansible_distribution == 'Debian' and ansible_distribution_version is version_compare('8', 'lt')
7 |
8 | - name: Add nginx apt repository for Debian < 8
9 | apt_repository:
10 | repo: 'deb [arch=amd64,i386] http://nginx.org/packages/debian/ {{ ansible_distribution_release }} nginx'
11 | state: present
12 | when: ansible_distribution == 'Debian' and ansible_distribution_version is version_compare('8', 'lt')
13 |
14 | - name: Ensure nginx is installed.
15 | apt:
16 | pkg: nginx
17 | state: present
18 | default_release: "{{ nginx_default_release }}"
19 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/tasks/setup-RedHat.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Enable nginx repo.
3 | template:
4 | src: nginx.repo.j2
5 | dest: /etc/yum.repos.d/nginx.repo
6 | owner: root
7 | group: root
8 | mode: 0644
9 |
10 | - name: Ensure nginx is installed.
11 | yum: pkg=nginx state=installed enablerepo=nginx
12 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/tasks/vhosts.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Remove default nginx vhost config file (if configured).
3 | file:
4 | path: "{{ nginx_default_vhost_path }}"
5 | state: absent
6 | when: nginx_remove_default_vhost
7 | notify: restart nginx
8 |
9 | - name: Add managed vhost config file (if any vhosts are configured).
10 | template:
11 | src: vhosts.j2
12 | dest: "{{ nginx_vhost_path }}/vhosts.conf"
13 | mode: 0644
14 | when: nginx_vhosts
15 | notify: restart nginx
16 |
17 | - name: Remove managed vhost config file (if no vhosts are configured).
18 | file:
19 | path: "{{ nginx_vhost_path }}/vhosts.conf"
20 | state: absent
21 | when: not nginx_vhosts
22 | notify: restart nginx
23 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/templates/nginx.conf.j2:
--------------------------------------------------------------------------------
1 | user {{ nginx_user }};
2 | worker_processes auto;
3 | worker_rlimit_nofile 65535;
4 |
5 | error_log /var/log/nginx/error.log warn;
6 | pid /var/run/nginx.pid;
7 |
8 |
9 | events {
10 | worker_connections {{ nginx_worker_connections or 2048 }};
11 | multi_accept on;
12 | }
13 |
14 |
15 | http {
16 | include /etc/nginx/mime.types;
17 | default_type application/octet-stream;
18 |
19 | log_format main '$remote_addr - $remote_user [$time_local] "$request" '
20 | '$status $body_bytes_sent "$http_referer" '
21 | '"$http_user_agent" "$http_x_forwarded_for"';
22 |
23 | access_log /var/log/nginx/access.log main;
24 |
25 | sendfile on;
26 | tcp_nopush on;
27 | tcp_nodelay on;
28 | server_tokens off;
29 |
30 | # keepalive_timeout 10;
31 | # keepalive_requests 10;
32 |
33 | gzip on;
34 | gzip_disable "msie6";
35 | gzip_http_version 1.1;
36 | gzip_comp_level 5;
37 | gzip_min_length 256;
38 | gzip_proxied any;
39 | gzip_vary on;
40 | gzip_types
41 | application/atom+xml
42 | application/javascript
43 | application/json
44 | application/rss+xml
45 | application/vnd.ms-fontobject
46 | application/x-font-ttf
47 | application/font-woff
48 | application/x-web-app-manifest+json
49 | application/xhtml+xml
50 | application/xml
51 | font/opentype
52 | image/svg+xml
53 | image/x-icon
54 | text/css
55 | text/plain
56 | text/x-component
57 | ;
58 |
59 | server_names_hash_max_size 4096;
60 |
61 | open_file_cache max=65000 inactive=1m;
62 | open_file_cache_valid 5s;
63 | open_file_cache_min_uses 1;
64 | open_file_cache_errors on;
65 |
66 | ssl_protocols SSLv3 TLSv1;
67 | ssl_ciphers ECDHE-RSA-AES256-SHA384:AES256-SHA256:RC4:HIGH:!MD5:!aNULL:!EDH:!AESGCM;
68 | ssl_prefer_server_ciphers on;
69 |
70 | client_max_body_size 50m;
71 | large_client_header_buffers 4 32k;
72 |
73 | proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=web-cache:8m max_size=1000m inactive=600m;
74 |
75 | include /etc/nginx/conf.d/*.conf;
76 | }
77 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/templates/nginx.repo.j2:
--------------------------------------------------------------------------------
1 | [nginx]
2 | name=nginx repo
3 | baseurl=http://nginx.org/packages/centos/{{ ansible_distribution_major_version }}/$basearch/
4 | gpgcheck=0
5 | enabled=1
6 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/templates/vhosts.j2:
--------------------------------------------------------------------------------
1 | {% for vhost in nginx_vhosts %}
2 | server {
3 | listen {{ vhost.listen | default('80 default_server') }};
4 | server_name {{ vhost.server_name }};
5 |
6 | root {{ vhost.root }};
7 | index {{ vhost.index | default('index.html index.htm') }};
8 |
9 | {% if vhost.error_page is defined %}
10 | error_page {{ vhost.error_page }};
11 | {% endif %}
12 | {% if vhost.access_log is defined %}
13 | access_log {{ vhost.access_log }};
14 | {% endif %}
15 |
16 | {% if vhost.return is defined %}
17 | return {{ vhost.return }};
18 | {% endif %}
19 |
20 | {% if vhost.extra_parameters is defined %}
21 | {{ vhost.extra_parameters }};
22 | {% endif %}
23 | }
24 | {% endfor %}
25 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/tests/inventory:
--------------------------------------------------------------------------------
1 | localhost
2 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/tests/test.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - hosts: localhost
3 | remote_user: root
4 | roles:
5 | - ansible-role-nginx
6 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/vars/Debian.yml:
--------------------------------------------------------------------------------
1 | ---
2 | nginx_vhost_path: /etc/nginx/sites-enabled
3 | nginx_default_vhost_path: /etc/nginx/sites-enabled/default
4 | __nginx_user: "www-data"
5 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nginx/vars/RedHat.yml:
--------------------------------------------------------------------------------
1 | ---
2 | nginx_vhost_path: /etc/nginx/conf.d
3 | nginx_default_vhost_path: /etc/nginx/conf.d/default.conf
4 | __nginx_user: "nginx"
5 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nodejs/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | node_version: 14
3 | ...
4 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nodejs/tasks/debian_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: 'Add Node.js PPA'
3 | tags: 'nodejs'
4 | become: 'yes'
5 | become_method: 'sudo'
6 | shell: "curl --silent --location https://deb.nodesource.com/setup_{{ node_version }}.x | bash -"
7 |
8 | - name: Install nodejs {{ node_version }}
9 | package:
10 | name: nodejs
11 | state: present
12 | ...
13 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/nodejs/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - include_tasks: debian_family.yml
3 | when: ansible_os_family == 'Debian'
4 |
5 | - include_tasks: redhat_family.yml
6 | when: ansible_os_family == "RedHat"
7 |
8 | - name: Install yarn
9 | command: npm install -g yarn
10 | become: yes
11 | become_user: root
12 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/nodejs/tasks/redhat_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: 'Add Node.js PPA'
3 | tags: 'nodejs'
4 | become: 'yes'
5 | become_method: 'sudo'
6 | shell: "curl --silent --location https://rpm.nodesource.com/setup_{{ node_version }}.x | sudo bash -"
7 |
8 | - name: Install node v{{ node_version }}
9 | yum: name=nodejs state=present
10 | when: ansible_os_family == 'RedHat'
11 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/ntpd/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install ntpd
3 | yum:
4 | name:
5 | - ntp
6 | - ntpdate
7 | state: present
8 | when: ansible_distribution == 'CentOS'
9 |
10 | - name: Enable ntpd
11 | service: name=ntpd enabled=yes state=started
12 | when: ansible_distribution == 'CentOS'
13 |
14 | - name: Install ntpd
15 | apt:
16 | pkg:
17 | - ntp
18 | - ntpdate
19 | state: present
20 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
21 |
22 | - name: Enable ntpd
23 | service: name=ntp enabled=yes state=started
24 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
25 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/packer/tasks/debian_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install unzip
3 | apt:
4 | pkg:
5 | - unzip
6 | update_cache: yes
7 | state: present
8 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/packer/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Check if packer already exists
3 | stat:
4 | path: /opt/packer
5 | register: packer
6 |
7 | - name: Check if packer version is 1.2.1
8 | command: /opt/packer --version
9 | register: packer_version
10 | when: packer.stat.exists
11 |
12 | - include_tasks: debian_family.yml
13 | when: ansible_os_family == 'Debian' and packer.stat.exists == False
14 |
15 | - include_tasks: redhat_family.yml
16 | when: ansible_os_family == "RedHat" and packer.stat.exists == False
17 |
18 | - name: Delete packer if < 1.2.1
19 | file:
20 | state: absent
21 | path: /opt/packer
22 | when: (packer.stat.exists) and (packer_version is version_compare('1.2.1', '<'))
23 |
24 | - name: Download packer zip file
25 | command: chdir=/opt/ wget https://releases.hashicorp.com/packer/1.2.1/packer_1.2.1_linux_amd64.zip
26 | when: (packer.stat.exists == False) or (packer_version is version_compare('1.2.1', '<'))
27 |
28 | - name: Unzip the packer binary in /opt
29 | command: chdir=/opt/ unzip packer_1.2.1_linux_amd64.zip
30 | when: (packer.stat.exists == False) or (packer_version is version_compare('1.2.1', '<'))
31 |
32 | - name: Remove the downloaded packer zip file
33 | file:
34 | state: absent
35 | path: /opt/packer_1.2.1_linux_amd64.zip
36 | when: (packer.stat.exists == False) or (packer_version is version_compare('1.2.1', '<'))
37 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/packer/tasks/redhat_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 |
3 | - name: Install unzip
4 | yum:
5 | name:
6 | - unzip
7 | state: present
8 | ...
9 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/psutil/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install psutil
3 | pip: name=psutil state=latest
--------------------------------------------------------------------------------
/bench/playbooks/roles/redis/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install yum packages
3 | yum:
4 | name:
5 | - redis
6 | state: present
7 | when: ansible_os_family == 'RedHat'
8 |
9 | # Prerequisite for Debian and Ubuntu
10 | - name: Install apt packages
11 | apt:
12 | pkg:
13 | - redis-server
14 | state: present
15 | force: yes
16 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
17 |
18 | # Prerequisite for MACOS
19 | - name: install prequisites for macos
20 | homebrew:
21 | name:
22 | - redis
23 | state: present
24 | when: ansible_distribution == 'MacOSX'
25 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/supervisor/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install supervisor on centos
3 | yum: name=supervisor state=present
4 | when: ansible_os_family == 'RedHat'
5 |
6 | - name: Install supervisor on debian
7 | apt: pkg=supervisor state=present force=yes
8 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu'
9 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/swap/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | swap_size_mb: 1024
3 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/swap/tasks/main.yml:
--------------------------------------------------------------------------------
1 | - name: Create swap space
2 | command: dd if=/dev/zero of=/extraswap bs=1M count={{swap_size_mb}}
3 | when: ansible_swaptotal_mb < 1
4 |
5 | - name: Make swap
6 | command: mkswap /extraswap
7 | when: ansible_swaptotal_mb < 1
8 |
9 | - name: Add to fstab
10 | action: lineinfile dest=/etc/fstab regexp="extraswap" line="/extraswap none swap sw 0 0" state=present
11 | when: ansible_swaptotal_mb < 1
12 |
13 | - name: Turn swap on
14 | command: swapon -a
15 | when: ansible_swaptotal_mb < 1
16 |
17 | - name: Set swapiness
18 | shell: echo 1 | tee /proc/sys/vm/swappiness
--------------------------------------------------------------------------------
/bench/playbooks/roles/virtualbox/defaults/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | virtualbox_version: 5.2
3 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/virtualbox/files/virtualbox_centos.repo:
--------------------------------------------------------------------------------
1 | [virtualbox]
2 | name=Oracle Linux / RHEL / CentOS-$releasever / $basearch - VirtualBox
3 | baseurl=http://download.virtualbox.org/virtualbox/rpm/el/$releasever/$basearch
4 | enabled=1
5 | gpgcheck=1
6 | repo_gpgcheck=1
7 | gpgkey=https://www.virtualbox.org/download/oracle_vbox.asc
--------------------------------------------------------------------------------
/bench/playbooks/roles/virtualbox/tasks/debian_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install dependencies
3 | apt:
4 | pkg:
5 | - apt-transport-https
6 | - ca-certificates
7 | state: present
8 |
9 | - name: Add VirtualBox to sources.list
10 | apt_repository:
11 | repo: deb https://download.virtualbox.org/virtualbox/debian {{ ansible_distribution_release }} contrib
12 | state: present
13 |
14 | - name: Add apt signing key for VirtualBox for Debian >= 8 and Ubuntu >= 16
15 | apt_key:
16 | url: https://www.virtualbox.org/download/oracle_vbox_2016.asc
17 | state: present
18 | when: (ansible_distribution == "Debian" and ansible_distribution_major_version >= "8") or (ansible_distribution == "Ubuntu" and ansible_distribution_major_version >= "16")
19 |
20 | - name: Add apt signing key for VirtualBox for Debian < 8 and Ubuntu < 16
21 | apt_key:
22 | url: https://www.virtualbox.org/download/oracle_vbox.asc
23 | state: present
24 | when: (ansible_distribution == "Debian" and ansible_distribution_major_version < "8") or (ansible_distribution == "Ubuntu" and ansible_distribution_major_version < "16")
25 |
26 | - name: Install VirtualBox
27 | apt:
28 | pkg:
29 | - virtualbox-{{ virtualbox_version }}
30 | update_cache: yes
31 | state: present
32 | ...
33 |
--------------------------------------------------------------------------------
/bench/playbooks/roles/virtualbox/tasks/main.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - include_tasks: debian_family.yml
3 | when: ansible_os_family == 'Debian'
4 |
5 | - include_tasks: redhat_family.yml
6 | when: ansible_os_family == "RedHat"
7 | ...
--------------------------------------------------------------------------------
/bench/playbooks/roles/virtualbox/tasks/redhat_family.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install the 'Development tools' package group
3 | yum:
4 | name: "@Development tools"
5 | state: present
6 |
7 | - name: Install dependencies
8 | yum:
9 | name:
10 | - kernel-devel
11 | - deltarpm
12 | state: present
13 |
14 | - copy: src=virtualbox_centos.repo dest=/etc/yum.repos.d/virtualbox.repo owner=root group=root mode=0644 force=no
15 |
16 | - name: Install VirtualBox
17 | command: yum install -y VirtualBox-{{ virtualbox_version }}
18 | ...
19 |
--------------------------------------------------------------------------------
/bench/playbooks/site.yml:
--------------------------------------------------------------------------------
1 | ---
2 | # This is the master playbook that deploys the whole Frappe and ERPNext stack
3 |
4 | - hosts: localhost
5 | become: yes
6 | become_user: root
7 | roles:
8 | - { role: common, tags: common }
9 | - { role: locale, tags: locale }
10 | - { role: mariadb, tags: mariadb }
11 | - { role: nodejs, tags: nodejs }
12 | - { role: swap, tags: swap, when: production and not container }
13 | - { role: logwatch, tags: logwatch, when: production }
14 | - { role: bash_screen_wall, tags: bash_screen_wall, when: production }
15 | - { role: frappe_selinux, tags: frappe_selinux, when: production }
16 | - { role: dns_caching, tags: dns_caching, when: production }
17 | - { role: ntpd, tags: ntpd, when: production }
18 | - { role: wkhtmltopdf, tags: wkhtmltopdf }
19 | - { role: psutil, tags: psutil }
20 | - { role: redis, tags: redis }
21 | - { role: supervisor, tags: supervisor, when: production }
22 | - { role: nginx, tags: nginx, when: production }
23 | - { role: fail2ban, tags: fail2ban, when: production }
24 | tasks:
25 | - name: Set hostname
26 | hostname: name='{{ hostname }}'
27 | when: hostname is defined and production
28 |
29 | - name: Start NTPD
30 | service: name=ntpd state=started
31 | when: ansible_distribution == 'CentOS' and production
32 |
33 | - name: Start NTPD
34 | service: name=ntp state=started
35 | when: ansible_distribution == 'Debian' or ansible_distribution == 'Ubuntu' and production
36 |
37 | - include_tasks: macosx.yml
38 | when: ansible_distribution == 'MacOSX'
39 |
40 | - name: setup bench and dev environment
41 | hosts: localhost
42 | vars:
43 | bench_repo_path: "{{ user_directory }}/.bench"
44 | bench_path: "{{ user_directory }}/{{ bench_name }}"
45 | roles:
46 | # setup frappe-bench
47 | - { role: bench, tags: "bench", when: not run_travis and not without_bench_setup }
48 | ...
49 |
--------------------------------------------------------------------------------
/bench/playbooks/vm_build.yml:
--------------------------------------------------------------------------------
1 | ---
2 | - name: Install Packer
3 | hosts: localhost
4 | become: yes
5 | become_user: root
6 | roles:
7 | - { role: virtualbox, tags: "virtualbox" }
8 | - { role: packer, tags: "packer" }
9 | ...
10 |
--------------------------------------------------------------------------------
/bench/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/frappe/bench/d771849e8ce16e85351904abbf0197c24bda8bfc/bench/tests/__init__.py
--------------------------------------------------------------------------------
/bench/tests/test_base.py:
--------------------------------------------------------------------------------
1 | # imports - standard imports
2 | import getpass
3 | import json
4 | import os
5 | import shutil
6 | import subprocess
7 | import sys
8 | import traceback
9 | import unittest
10 |
11 | # imports - module imports
12 | from bench.utils import paths_in_bench, exec_cmd
13 | from bench.utils.system import init
14 | from bench.bench import Bench
15 |
16 | PYTHON_VER = sys.version_info
17 |
18 | FRAPPE_BRANCH = "version-13-hotfix"
19 | if PYTHON_VER.major == 3:
20 | if PYTHON_VER.minor >= 10:
21 | FRAPPE_BRANCH = "develop"
22 |
23 |
24 | class TestBenchBase(unittest.TestCase):
25 | def setUp(self):
26 | self.benches_path = "."
27 | self.benches = []
28 |
29 | def tearDown(self):
30 | for bench_name in self.benches:
31 | bench_path = os.path.join(self.benches_path, bench_name)
32 | bench = Bench(bench_path)
33 | mariadb_password = (
34 | "travis"
35 | if os.environ.get("CI")
36 | else getpass.getpass(prompt="Enter MariaDB root Password: ")
37 | )
38 |
39 | if bench.exists:
40 | for site in bench.sites:
41 | subprocess.call(
42 | [
43 | "bench",
44 | "drop-site",
45 | site,
46 | "--force",
47 | "--no-backup",
48 | "--root-password",
49 | mariadb_password,
50 | ],
51 | cwd=bench_path,
52 | )
53 | shutil.rmtree(bench_path, ignore_errors=True)
54 |
55 | def assert_folders(self, bench_name):
56 | for folder in paths_in_bench:
57 | self.assert_exists(bench_name, folder)
58 | self.assert_exists(bench_name, "apps", "frappe")
59 |
60 | def assert_virtual_env(self, bench_name):
61 | bench_path = os.path.abspath(bench_name)
62 | python_path = os.path.abspath(os.path.join(bench_path, "env", "bin", "python"))
63 | self.assertTrue(python_path.startswith(bench_path))
64 | for subdir in ("bin", "lib", "share"):
65 | self.assert_exists(bench_name, "env", subdir)
66 |
67 | def assert_config(self, bench_name):
68 | for config, search_key in (
69 | ("redis_queue.conf", "redis_queue.rdb"),
70 | ("redis_cache.conf", "redis_cache.rdb"),
71 | ):
72 |
73 | self.assert_exists(bench_name, "config", config)
74 |
75 | with open(os.path.join(bench_name, "config", config)) as f:
76 | self.assertTrue(search_key in f.read())
77 |
78 | def assert_common_site_config(self, bench_name, expected_config):
79 | common_site_config_path = os.path.join(
80 | self.benches_path, bench_name, "sites", "common_site_config.json"
81 | )
82 | self.assertTrue(os.path.exists(common_site_config_path))
83 |
84 | with open(common_site_config_path) as f:
85 | config = json.load(f)
86 |
87 | for key, value in list(expected_config.items()):
88 | self.assertEqual(config.get(key), value)
89 |
90 | def assert_exists(self, *args):
91 | self.assertTrue(os.path.exists(os.path.join(*args)))
92 |
93 | def new_site(self, site_name, bench_name):
94 | new_site_cmd = ["bench", "new-site", site_name, "--admin-password", "admin"]
95 |
96 | if os.environ.get("CI"):
97 | new_site_cmd.extend(["--mariadb-root-password", "travis"])
98 |
99 | subprocess.call(new_site_cmd, cwd=os.path.join(self.benches_path, bench_name))
100 |
101 | def init_bench(self, bench_name, **kwargs):
102 | self.benches.append(bench_name)
103 | frappe_tmp_path = "/tmp/frappe"
104 |
105 | if not os.path.exists(frappe_tmp_path):
106 | exec_cmd(
107 | f"git clone https://github.com/frappe/frappe -b {FRAPPE_BRANCH} --depth 1 --origin upstream {frappe_tmp_path}"
108 | )
109 |
110 | kwargs.update(
111 | dict(
112 | python=sys.executable,
113 | no_procfile=True,
114 | no_backups=True,
115 | frappe_path=frappe_tmp_path,
116 | )
117 | )
118 |
119 | if not os.path.exists(os.path.join(self.benches_path, bench_name)):
120 | init(bench_name, **kwargs)
121 | exec_cmd(
122 | "git remote set-url upstream https://github.com/frappe/frappe",
123 | cwd=os.path.join(self.benches_path, bench_name, "apps", "frappe"),
124 | )
125 |
126 | def file_exists(self, path):
127 | if os.environ.get("CI"):
128 | return not subprocess.call(["sudo", "test", "-f", path])
129 | return os.path.isfile(path)
130 |
131 | def get_traceback(self):
132 | exc_type, exc_value, exc_tb = sys.exc_info()
133 | trace_list = traceback.format_exception(exc_type, exc_value, exc_tb)
134 | return "".join(str(t) for t in trace_list)
135 |
--------------------------------------------------------------------------------
/bench/tests/test_setup_production.py:
--------------------------------------------------------------------------------
1 | # imports - standard imports
2 | import getpass
3 | import os
4 | import pathlib
5 | import re
6 | import subprocess
7 | import time
8 | import unittest
9 |
10 | # imports - module imports
11 | from bench.utils import exec_cmd, get_cmd_output, which
12 | from bench.config.production_setup import get_supervisor_confdir
13 | from bench.tests.test_base import TestBenchBase
14 |
15 |
16 | class TestSetupProduction(TestBenchBase):
17 | def test_setup_production(self):
18 | user = getpass.getuser()
19 |
20 | for bench_name in ("test-bench-1", "test-bench-2"):
21 | bench_path = os.path.join(os.path.abspath(self.benches_path), bench_name)
22 | self.init_bench(bench_name)
23 | exec_cmd(f"sudo bench setup production {user} --yes", cwd=bench_path)
24 | self.assert_nginx_config(bench_name)
25 | self.assert_supervisor_config(bench_name)
26 | self.assert_supervisor_process(bench_name)
27 |
28 | self.assert_nginx_process()
29 | exec_cmd(f"sudo bench setup sudoers {user}")
30 | self.assert_sudoers(user)
31 |
32 | for bench_name in self.benches:
33 | bench_path = os.path.join(os.path.abspath(self.benches_path), bench_name)
34 | exec_cmd("sudo bench disable-production", cwd=bench_path)
35 |
36 | def production(self):
37 | try:
38 | self.test_setup_production()
39 | except Exception:
40 | print(self.get_traceback())
41 |
42 | def assert_nginx_config(self, bench_name):
43 | conf_src = os.path.join(
44 | os.path.abspath(self.benches_path), bench_name, "config", "nginx.conf"
45 | )
46 | conf_dest = f"/etc/nginx/conf.d/{bench_name}.conf"
47 |
48 | self.assertTrue(self.file_exists(conf_src))
49 | self.assertTrue(self.file_exists(conf_dest))
50 |
51 | # symlink matches
52 | self.assertEqual(os.path.realpath(conf_dest), conf_src)
53 |
54 | # file content
55 | with open(conf_src) as f:
56 | f = f.read()
57 |
58 | for key in (
59 | f"upstream {bench_name}-frappe",
60 | f"upstream {bench_name}-socketio-server",
61 | ):
62 | self.assertTrue(key in f)
63 |
64 | def assert_nginx_process(self):
65 | out = get_cmd_output("sudo nginx -t 2>&1")
66 | self.assertTrue(
67 | "nginx: configuration file /etc/nginx/nginx.conf test is successful" in out
68 | )
69 |
70 | def assert_sudoers(self, user):
71 | sudoers_file = "/etc/sudoers.d/frappe"
72 | service = which("service")
73 | nginx = which("nginx")
74 |
75 | self.assertTrue(self.file_exists(sudoers_file))
76 |
77 | if os.environ.get("CI"):
78 | sudoers = subprocess.check_output(["sudo", "cat", sudoers_file]).decode("utf-8")
79 | else:
80 | sudoers = pathlib.Path(sudoers_file).read_text()
81 | self.assertTrue(f"{user} ALL = (root) NOPASSWD: {service} nginx *" in sudoers)
82 | self.assertTrue(f"{user} ALL = (root) NOPASSWD: {nginx}" in sudoers)
83 |
84 | def assert_supervisor_config(self, bench_name, use_rq=True):
85 | conf_src = os.path.join(
86 | os.path.abspath(self.benches_path), bench_name, "config", "supervisor.conf"
87 | )
88 |
89 | supervisor_conf_dir = get_supervisor_confdir()
90 | conf_dest = f"{supervisor_conf_dir}/{bench_name}.conf"
91 |
92 | self.assertTrue(self.file_exists(conf_src))
93 | self.assertTrue(self.file_exists(conf_dest))
94 |
95 | # symlink matches
96 | self.assertEqual(os.path.realpath(conf_dest), conf_src)
97 |
98 | # file content
99 | with open(conf_src) as f:
100 | f = f.read()
101 |
102 | tests = [
103 | f"program:{bench_name}-frappe-web",
104 | f"program:{bench_name}-redis-cache",
105 | f"program:{bench_name}-redis-queue",
106 | f"group:{bench_name}-web",
107 | f"group:{bench_name}-workers",
108 | f"group:{bench_name}-redis",
109 | ]
110 |
111 | if not os.environ.get("CI"):
112 | tests.append(f"program:{bench_name}-node-socketio")
113 |
114 | if use_rq:
115 | tests.extend(
116 | [
117 | f"program:{bench_name}-frappe-schedule",
118 | f"program:{bench_name}-frappe-default-worker",
119 | f"program:{bench_name}-frappe-short-worker",
120 | f"program:{bench_name}-frappe-long-worker",
121 | ]
122 | )
123 |
124 | else:
125 | tests.extend(
126 | [
127 | f"program:{bench_name}-frappe-workerbeat",
128 | f"program:{bench_name}-frappe-worker",
129 | f"program:{bench_name}-frappe-longjob-worker",
130 | f"program:{bench_name}-frappe-async-worker",
131 | ]
132 | )
133 |
134 | for key in tests:
135 | self.assertTrue(key in f)
136 |
137 | def assert_supervisor_process(self, bench_name, use_rq=True, disable_production=False):
138 | out = get_cmd_output("supervisorctl status")
139 |
140 | while "STARTING" in out:
141 | print("Waiting for all processes to start...")
142 | time.sleep(10)
143 | out = get_cmd_output("supervisorctl status")
144 |
145 | tests = [
146 | r"{bench_name}-web:{bench_name}-frappe-web[\s]+RUNNING",
147 | # Have commented for the time being. Needs to be uncommented later on. Bench is failing on travis because of this.
148 | # It works on one bench and fails on another.giving FATAL or BACKOFF (Exited too quickly (process log may have details))
149 | # "{bench_name}-web:{bench_name}-node-socketio[\s]+RUNNING",
150 | r"{bench_name}-redis:{bench_name}-redis-cache[\s]+RUNNING",
151 | r"{bench_name}-redis:{bench_name}-redis-queue[\s]+RUNNING",
152 | ]
153 |
154 | if use_rq:
155 | tests.extend(
156 | [
157 | r"{bench_name}-workers:{bench_name}-frappe-schedule[\s]+RUNNING",
158 | r"{bench_name}-workers:{bench_name}-frappe-default-worker-0[\s]+RUNNING",
159 | r"{bench_name}-workers:{bench_name}-frappe-short-worker-0[\s]+RUNNING",
160 | r"{bench_name}-workers:{bench_name}-frappe-long-worker-0[\s]+RUNNING",
161 | ]
162 | )
163 |
164 | else:
165 | tests.extend(
166 | [
167 | r"{bench_name}-workers:{bench_name}-frappe-workerbeat[\s]+RUNNING",
168 | r"{bench_name}-workers:{bench_name}-frappe-worker[\s]+RUNNING",
169 | r"{bench_name}-workers:{bench_name}-frappe-longjob-worker[\s]+RUNNING",
170 | r"{bench_name}-workers:{bench_name}-frappe-async-worker[\s]+RUNNING",
171 | ]
172 | )
173 |
174 | for key in tests:
175 | if disable_production:
176 | self.assertFalse(re.search(key, out))
177 | else:
178 | self.assertTrue(re.search(key, out))
179 |
180 |
181 | if __name__ == "__main__":
182 | unittest.main()
183 |
--------------------------------------------------------------------------------
/bench/tests/test_utils.py:
--------------------------------------------------------------------------------
1 | import os
2 | import shutil
3 | import subprocess
4 | import unittest
5 |
6 | from bench.app import App
7 | from bench.bench import Bench
8 | from bench.exceptions import InvalidRemoteException
9 | from bench.utils import is_valid_frappe_branch
10 |
11 |
12 | class TestUtils(unittest.TestCase):
13 | def test_app_utils(self):
14 | git_url = "https://github.com/frappe/frappe"
15 | branch = "develop"
16 | app = App(name=git_url, branch=branch, bench=Bench("."))
17 | self.assertTrue(
18 | all(
19 | [
20 | app.name == git_url,
21 | app.branch == branch,
22 | app.tag == branch,
23 | app.is_url is True,
24 | app.on_disk is False,
25 | app.org == "frappe",
26 | app.url == git_url,
27 | ]
28 | )
29 | )
30 |
31 | def test_is_valid_frappe_branch(self):
32 | with self.assertRaises(InvalidRemoteException):
33 | is_valid_frappe_branch(
34 | "https://github.com/frappe/frappe.git", frappe_branch="random-branch"
35 | )
36 | is_valid_frappe_branch(
37 | "https://github.com/random/random.git", frappe_branch="random-branch"
38 | )
39 |
40 | is_valid_frappe_branch(
41 | "https://github.com/frappe/frappe.git", frappe_branch="develop"
42 | )
43 | is_valid_frappe_branch(
44 | "https://github.com/frappe/frappe.git", frappe_branch="v13.29.0"
45 | )
46 |
47 | def test_app_states(self):
48 | bench_dir = "./sandbox"
49 | sites_dir = os.path.join(bench_dir, "sites")
50 |
51 | if not os.path.exists(sites_dir):
52 | os.makedirs(sites_dir)
53 |
54 | fake_bench = Bench(bench_dir)
55 |
56 | self.assertTrue(hasattr(fake_bench.apps, "states"))
57 |
58 | fake_bench.apps.states = {
59 | "frappe": {
60 | "resolution": {"branch": "develop", "commit_hash": "234rwefd"},
61 | "version": "14.0.0-dev",
62 | }
63 | }
64 | fake_bench.apps.update_apps_states()
65 |
66 | self.assertEqual(fake_bench.apps.states, {})
67 |
68 | frappe_path = os.path.join(bench_dir, "apps", "frappe")
69 |
70 | os.makedirs(os.path.join(frappe_path, "frappe"))
71 |
72 | subprocess.run(["git", "init"], cwd=frappe_path, capture_output=True, check=True)
73 |
74 | with open(os.path.join(frappe_path, "frappe", "__init__.py"), "w+") as f:
75 | f.write("__version__ = '11.0'")
76 |
77 | subprocess.run(["git", "add", "."], cwd=frappe_path, capture_output=True, check=True)
78 | subprocess.run(
79 | ["git", "config", "user.email", "bench-test_app_states@gha.com"],
80 | cwd=frappe_path,
81 | capture_output=True,
82 | check=True,
83 | )
84 | subprocess.run(
85 | ["git", "config", "user.name", "App States Test"],
86 | cwd=frappe_path,
87 | capture_output=True,
88 | check=True,
89 | )
90 | subprocess.run(
91 | ["git", "commit", "-m", "temp"], cwd=frappe_path, capture_output=True, check=True
92 | )
93 |
94 | fake_bench.apps.update_apps_states(app_name="frappe")
95 |
96 | self.assertIn("frappe", fake_bench.apps.states)
97 | self.assertIn("version", fake_bench.apps.states["frappe"])
98 | self.assertEqual("11.0", fake_bench.apps.states["frappe"]["version"])
99 |
100 | shutil.rmtree(bench_dir)
101 |
102 | def test_ssh_ports(self):
103 | app = App("git@github.com:22:frappe/frappe")
104 | self.assertEqual(
105 | (app.use_ssh, app.org, app.repo, app.app_name), (True, "frappe", "frappe", "frappe")
106 | )
107 |
--------------------------------------------------------------------------------
/bench/utils/cli.py:
--------------------------------------------------------------------------------
1 | from typing import List
2 | import click
3 | from click.core import _check_nested_chain
4 |
5 |
6 | def print_bench_version(ctx, param, value):
7 | """Prints current bench version"""
8 | if not value or ctx.resilient_parsing:
9 | return
10 |
11 | import bench
12 |
13 | click.echo(bench.VERSION)
14 | ctx.exit()
15 |
16 |
17 | class MultiCommandGroup(click.Group):
18 | def add_command(self, cmd, name=None):
19 | """Registers another :class:`Command` with this group. If the name
20 | is not provided, the name of the command is used.
21 |
22 | Note: This is a custom Group that allows passing a list of names for
23 | the command name.
24 | """
25 | name = name or cmd.name
26 | if name is None:
27 | raise TypeError("Command has no name.")
28 | _check_nested_chain(self, name, cmd, register=True)
29 |
30 | try:
31 | self.commands[name] = cmd
32 | except TypeError:
33 | if isinstance(name, list):
34 | for _name in name:
35 | self.commands[_name] = cmd
36 |
37 |
38 | class SugaredOption(click.Option):
39 | def __init__(self, *args, **kwargs):
40 | self.only_if_set: List = kwargs.pop("only_if_set")
41 | kwargs["help"] = (
42 | kwargs.get("help", "")
43 | + f". Option is acceptable only if {', '.join(self.only_if_set)} is used."
44 | )
45 | super().__init__(*args, **kwargs)
46 |
47 | def handle_parse_result(self, ctx, opts, args):
48 | current_opt = self.name in opts
49 | if current_opt and self.only_if_set:
50 | for opt in self.only_if_set:
51 | if opt not in opts:
52 | deafaults_set = [x.default for x in ctx.command.params if x.name == opt]
53 | if not deafaults_set:
54 | raise click.UsageError(f"Illegal Usage: Set '{opt}' before '{self.name}'.")
55 |
56 | return super().handle_parse_result(ctx, opts, args)
57 |
58 |
59 | def use_experimental_feature(ctx, param, value):
60 | if not value:
61 | return
62 |
63 | if value == "dynamic-feed":
64 | import bench.cli
65 |
66 | bench.cli.dynamic_feed = True
67 | bench.cli.verbose = True
68 | else:
69 | from bench.exceptions import FeatureDoesNotExistError
70 |
71 | raise FeatureDoesNotExistError(f"Feature {value} does not exist")
72 |
73 | from bench.cli import is_envvar_warn_set
74 |
75 | if is_envvar_warn_set:
76 | return
77 |
78 | click.secho(
79 | "WARNING: bench is using it's new CLI rendering engine. This behaviour has"
80 | f" been enabled by passing --{value} in the command. This feature is"
81 | " experimental and may not be implemented for all commands yet.",
82 | fg="yellow",
83 | )
84 |
85 |
86 | def setup_verbosity(ctx, param, value):
87 | if not value:
88 | return
89 |
90 | import bench.cli
91 |
92 | bench.cli.verbose = True
93 |
--------------------------------------------------------------------------------
/bench/utils/render.py:
--------------------------------------------------------------------------------
1 | # imports - standard imports
2 | import sys
3 | from io import StringIO
4 |
5 | # imports - third party imports
6 | import click
7 |
8 | # imports - module imports
9 | import bench
10 |
11 |
12 | class Capturing(list):
13 | """
14 | Util to consume the stdout encompassed in it and push it to a list
15 |
16 | with Capturing() as output:
17 | subprocess.check_output("ls", shell=True)
18 |
19 | print(output)
20 | # ["b'Applications\\nDesktop\\nDocuments\\nDownloads\\n'"]
21 | """
22 |
23 | def __enter__(self):
24 | self._stdout = sys.stdout
25 | sys.stdout = self._stringio = StringIO()
26 | return self
27 |
28 | def __exit__(self, *args):
29 | self.extend(self._stringio.getvalue().splitlines())
30 | del self._stringio # free up some memory
31 | sys.stdout = self._stdout
32 |
33 |
34 | class Rendering:
35 | def __init__(self, success, title, is_parent, args, kwargs):
36 | import bench.cli
37 |
38 | self.dynamic_feed = bench.cli.from_command_line and bench.cli.dynamic_feed
39 |
40 | if not self.dynamic_feed:
41 | return
42 |
43 | try:
44 | self.kw = args[0].__dict__
45 | except Exception:
46 | self.kw = kwargs
47 |
48 | self.is_parent = is_parent
49 | self.title = title
50 | self.success = success
51 |
52 | def __enter__(self, *args, **kwargs):
53 | if not self.dynamic_feed:
54 | return
55 |
56 | _prefix = click.style("⏼", fg="bright_yellow")
57 | _hierarchy = "" if self.is_parent else " "
58 | self._title = self.title.format(**self.kw)
59 | click.secho(f"{_hierarchy}{_prefix} {self._title}")
60 |
61 | bench.LOG_BUFFER.append(
62 | {
63 | "message": self._title,
64 | "prefix": _prefix,
65 | "color": None,
66 | "is_parent": self.is_parent,
67 | }
68 | )
69 |
70 | def __exit__(self, *args, **kwargs):
71 | if not self.dynamic_feed:
72 | return
73 |
74 | self._prefix = click.style("✔", fg="green")
75 | self._success = self.success.format(**self.kw)
76 |
77 | self.render_screen()
78 |
79 | def render_screen(self):
80 | click.clear()
81 |
82 | for l in bench.LOG_BUFFER:
83 | if l["message"] == self._title:
84 | l["prefix"] = self._prefix
85 | l["message"] = self._success
86 | _hierarchy = "" if l.get("is_parent") else " "
87 | click.secho(f'{_hierarchy}{l["prefix"]} {l["message"]}', fg=l["color"])
88 |
89 |
90 | def job(title: str = None, success: str = None):
91 | """Supposed to be wrapped around an atomic job in a given process.
92 | For instance, the `get-app` command consists of two jobs: `initializing bench`
93 | and `fetching and installing app`.
94 | """
95 |
96 | def innfn(fn):
97 | def wrapper_fn(*args, **kwargs):
98 | with Rendering(
99 | success=success,
100 | title=title,
101 | is_parent=True,
102 | args=args,
103 | kwargs=kwargs,
104 | ):
105 | return fn(*args, **kwargs)
106 |
107 | return wrapper_fn
108 |
109 | return innfn
110 |
111 |
112 | def step(title: str = None, success: str = None):
113 | """Supposed to be wrapped around the smallest possible atomic step in a given operation.
114 | For instance, `building assets` is a step in the update operation.
115 | """
116 |
117 | def innfn(fn):
118 | def wrapper_fn(*args, **kwargs):
119 | with Rendering(
120 | success=success,
121 | title=title,
122 | is_parent=False,
123 | args=args,
124 | kwargs=kwargs,
125 | ):
126 | return fn(*args, **kwargs)
127 |
128 | return wrapper_fn
129 |
130 | return innfn
131 |
--------------------------------------------------------------------------------
/bench/utils/translation.py:
--------------------------------------------------------------------------------
1 | # imports - standard imports
2 | import itertools
3 | import json
4 | import os
5 |
6 |
7 | def update_translations_p(args):
8 | import requests
9 |
10 | try:
11 | update_translations(*args)
12 | except requests.exceptions.HTTPError:
13 | print("Download failed for", args[0], args[1])
14 |
15 |
16 | def download_translations_p():
17 | import multiprocessing
18 |
19 | pool = multiprocessing.Pool(multiprocessing.cpu_count())
20 |
21 | langs = get_langs()
22 | apps = ("frappe", "erpnext")
23 | args = list(itertools.product(apps, langs))
24 |
25 | pool.map(update_translations_p, args)
26 |
27 |
28 | def download_translations():
29 | langs = get_langs()
30 | apps = ("frappe", "erpnext")
31 | for app, lang in itertools.product(apps, langs):
32 | update_translations(app, lang)
33 |
34 |
35 | def get_langs():
36 | lang_file = "apps/frappe/frappe/geo/languages.json"
37 | with open(lang_file) as f:
38 | langs = json.loads(f.read())
39 | return [d["code"] for d in langs]
40 |
41 |
42 | def update_translations(app, lang):
43 | import requests
44 |
45 | translations_dir = os.path.join("apps", app, app, "translations")
46 | csv_file = os.path.join(translations_dir, f"{lang}.csv")
47 | url = f"https://translate.erpnext.com/files/{app}-{lang}.csv"
48 | r = requests.get(url, stream=True)
49 | r.raise_for_status()
50 |
51 | with open(csv_file, "wb") as f:
52 | for chunk in r.iter_content(chunk_size=1024):
53 | # filter out keep-alive new chunks
54 | if chunk:
55 | f.write(chunk)
56 | f.flush()
57 |
58 | print("downloaded for", app, lang)
59 |
--------------------------------------------------------------------------------
/completion.sh:
--------------------------------------------------------------------------------
1 | _bench_completion() {
2 | # Complete commands using click bashcomplete
3 | COMPREPLY=( $( COMP_WORDS="${COMP_WORDS[*]}" \
4 | COMP_CWORD=$COMP_CWORD \
5 | _BENCH_COMPLETE=complete $1 ) )
6 | if [ -d "sites" ]; then
7 | # Also add frappe commands if present
8 |
9 | # bench_helper.py expects to be executed from "sites" directory
10 | cd sites
11 |
12 | # All frappe commands are subcommands under "bench frappe"
13 | # Frappe is only installed in virtualenv "env" so use appropriate python executable
14 | COMPREPLY+=( $( COMP_WORDS="bench frappe "${COMP_WORDS[@]:1} \
15 | COMP_CWORD=$(($COMP_CWORD+1)) \
16 | _BENCH_COMPLETE=complete ../env/bin/python ../apps/frappe/frappe/utils/bench_helper.py ) )
17 |
18 | # If the word before the current cursor position in command typed so far is "--site" then only list sites
19 | if [ ${COMP_WORDS[COMP_CWORD-1]} == "--site" ]; then
20 | COMPREPLY=( $( ls -d ./*/site_config.json | cut -f 2 -d "/" | xargs echo ) )
21 | fi
22 |
23 | # Get out of sites directory now
24 | cd ..
25 | fi
26 | return 0
27 | }
28 |
29 | # Only support bash and zsh
30 | if [ -n "$BASH" ] ; then
31 | complete -F _bench_completion -o default bench;
32 | elif [ -n "$ZSH_VERSION" ]; then
33 | # Use zsh in bash compatibility mode
34 | autoload bashcompinit
35 | bashcompinit
36 | complete -F _bench_completion -o default bench;
37 | fi
38 |
--------------------------------------------------------------------------------
/docs/bench_custom_cmd.md:
--------------------------------------------------------------------------------
1 | ## How are Frappe Framework commands available via bench?
2 |
3 | bench utilizes `frappe.utils.bench_manager` to get the framework's as well as those of any custom commands written in application installed in the Frappe environment. Currently, with *version 12* there are commands related to the scheduler, sites, translations and other utils in Frappe inherited by bench.
4 |
5 |
6 | ## Can I add CLI commands in my custom app and call them via bench?
7 |
8 | Along with the framework commands, Frappe's `bench_manager` module also searches for any commands in your custom applications. Thereby, bench communicates with the respective bench's Frappe which in turn checks for available commands in all of the applications.
9 |
10 | To make your custom command available to bench, just create a `commands` module under your parent module and write the command with a click wrapper and a variable commands which contains a list of click functions, which are your own commands. The directory structure may be visualized as:
11 |
12 | ```
13 | frappe-bench
14 | |──apps
15 | |── frappe
16 | ├── custom_app
17 | │ ├── README.md
18 | │ ├── custom_app
19 | │ │ ├── commands <------ commands module
20 | │ ├── license.txt
21 | │ ├── requirements.txt
22 | │ └── setup.py
23 | ```
24 |
25 | The commands module maybe a single file such as `commands.py` or a directory with an `__init__.py` file. For a custom application of name 'flags', example may be given as
26 |
27 | ```python
28 | # file_path: frappe-bench/apps/flags/flags/commands.py
29 | import click
30 |
31 | @click.command('set-flags')
32 | @click.argument('state', type=click.Choice(['on', 'off']))
33 | def set_flags(state):
34 | from flags.utils import set_flags
35 | set_flags(state=state)
36 |
37 | commands = [
38 | set_flags
39 | ]
40 | ```
41 |
42 | and with context of the current bench, this command maybe executed simply as
43 |
44 | ```zsh
45 | ➜ bench set-flags
46 | Flags are set to state: 'on'
47 | ```
48 |
49 |
--------------------------------------------------------------------------------
/docs/branch_details.md:
--------------------------------------------------------------------------------
1 | ### ERPNext/Frappe Branching
2 |
3 | #### Branch Description
4 | - `develop` Branch: All new feature developments will go in develop branch
5 | - `staging` Branch: This branch serves as a release candidate. Before a week, release team will pull the feature from develop branch to staging branch.
6 | EG: if the feature is in 25 July's milestone then it should go in staging on 19th July.
7 | - `master` Branch: Community release.
8 | - `hotfix` Branch: mainly define for support issues. This will include bugs or any high priority task like security patches.
9 |
10 | #### Where to send PR?
11 | - If you are working on a new feature, then PR should point to develop branch
12 | - If you are working on support issue / bug / error report, then PR should point to hotfix brach
13 | - While performing testing on Staging branch, if any fix needed then only send that fix PR to staging.
--------------------------------------------------------------------------------
/docs/commands_and_usage.md:
--------------------------------------------------------------------------------
1 | ## Usage
2 |
3 | * Updating
4 |
5 | To update the bench CLI tool, depending on your method of installation, you may use
6 |
7 | pip3 install -U frappe-bench
8 |
9 |
10 | To backup, update all apps and sites on your bench, you may use
11 |
12 | bench update
13 |
14 |
15 | To manually update the bench, run `bench update` to update all the apps, run
16 | patches, build JS and CSS files and restart supervisor (if configured to).
17 |
18 | You can also run the parts of the bench selectively.
19 |
20 | `bench update --pull` will only pull changes in the apps
21 |
22 | `bench update --patch` will only run database migrations in the apps
23 |
24 | `bench update --build` will only build JS and CSS files for the bench
25 |
26 | `bench update --bench` will only update the bench utility (this project)
27 |
28 | `bench update --requirements` will only update all dependencies (Python + Node) for the apps available in current bench
29 |
30 |
31 | * Create a new bench
32 |
33 | The init command will create a bench directory with frappe framework installed. It will be setup for periodic backups and auto updates once a day.
34 |
35 | bench init frappe-bench && cd frappe-bench
36 |
37 | * Add a site
38 |
39 | Frappe apps are run by frappe sites and you will have to create at least one site. The new-site command allows you to do that.
40 |
41 | bench new-site site1.local
42 |
43 | * Add apps
44 |
45 | The get-app command gets remote frappe apps from a remote git repository and installs them. Example: [erpnext](https://github.com/frappe/erpnext)
46 |
47 | bench get-app erpnext https://github.com/frappe/erpnext
48 |
49 | * Install apps
50 |
51 | To install an app on your new site, use the bench `install-app` command.
52 |
53 | bench --site site1.local install-app erpnext
54 |
55 | * Start bench
56 |
57 | To start using the bench, use the `bench start` command
58 |
59 | bench start
60 |
61 | To login to Frappe / ERPNext, open your browser and go to `[your-external-ip]:8000`, probably `localhost:8000`
62 |
63 | The default username is "Administrator" and password is what you set when you created the new site.
64 |
65 | * Setup Manager
66 |
67 | ## What it does
68 |
69 | bench setup manager
70 |
71 | 1. Create new site bench-manager.local
72 | 2. Gets the `bench_manager` app from https://github.com/frappe/bench_manager if it doesn't exist already
73 | 3. Installs the bench_manager app on the site bench-manager.local
74 |
75 |
--------------------------------------------------------------------------------
/docs/contribution_guidelines.md:
--------------------------------------------------------------------------------
1 | # Contribution Guidelines
2 |
3 | ### Introduction (for first timers)
4 |
5 | Thank you for your interest in contributing to our project! Our world works on people taking initiative to contribute to the "commons" and contributing to open source means you are contributing to make things better for not only yourself, but everyone else too! So kudos to you for taking this initiative.
6 |
7 | Great projects depend on good code quality and adhering to certain standards while making sure the goals of the project are met. New features should follow the same pattern and so that users don't have to learn things again and again.
8 |
9 | Developers who maintain open source also expect that you follow certain guidelines. These guidelines ensure that developers are able quickly give feedback on your contribution and how to make it better. Most probably you might have to go back and change a few things, but it will be in th interest of making this process better for everyone. So do be prepared for some back and forth.
10 |
11 | Happy contributing!
12 |
13 | ### Feedback Policy
14 |
15 | We will strive for a "Zero Pull Request Pending" policy, inspired by "Zero Inbox". This means, that if the pull request is good, it will be merged within a day and if it does not meet the requirements, it will be closed.
16 |
17 | ### Design Guides
18 |
19 | Please read the following design guidelines carefully when contributing:
20 |
21 | 1. [Form Design Guidelines](https://github.com/frappe/erpnext/wiki/Form-Design-Guidelines)
22 | 1. [How to break large contributions into smaller ones](https://github.com/frappe/erpnext/wiki/Cascading-Pull-Requests)
23 |
24 | ### Pull Request Requirements
25 |
26 | 1. **Test Cases:** Important to add test cases, even if its a very simple one that just calls the function. For UI, till we don't have Selenium testing setup, we need to see a screenshot / animated GIF.
27 | 1. **UX:** If your change involves user experience, add a screenshot / narration / animated GIF.
28 | 1. **Documentation:** Test Case must involve updating necessary documentation
29 | 1. **Explanation:** Include explanation if there is a design change, explain the use case and why this suggested change is better. If you are including a new library or replacing one, please give sufficient reference of why the suggested library is better.
30 | 1. **Demo:** Remember to update the demo script so that data related your feature is included in the demo.
31 | 1. **Failing Tests:** This is simple, you must make sure all automated tests are passing.
32 | 1. **Very Large Contribution:** It is very hard to accept and merge very large contributions, because there are too many lines of code to check and its implications can be large and unexpected. They way to contribute big features is to build them part by part. We can understand there are exceptions, but in most cases try and keep your pull-request to **30 lines of code** excluding tests and config files. **Use [Cascading Pull Requests](https://github.com/frappe/erpnext/wiki/Cascading-Pull-Requests)** for large features.
33 | 1. **Incomplete Contributions must be hidden:** If the contribution is WIP or incomplete - which will most likely be the case, you can send small PRs as long as the user is not exposed to unfinished functionality. This will ensure that your code does not have build or other collateral issues. But these features must remain completely hidden to the user.
34 | 1. **Incorrect Patches:** If your design involves schema change and you must include patches that update the data as per your new schema.
35 | 1. **Incorrect Naming:** The naming of variables, models, fields etc must be consistent as per the existing design and semantics used in the system.
36 | 1. **Translated Strings:** All user facing strings / text must be wrapped in the `__("")` function in javascript and `_("")` function in Python, so that it is shown as translated to the user.
37 | 1. **Deprecated API:** The API used in the pull request must be the latest recommended methods and usage of globals like `cur_frm` must be avoided.
38 | 1. **Whitespace and indentation:** The ERPNext and Frappe Project uses tabs (I know and we are sorry, but its too much effort to change it now and we don't want to lose the history). The indentation must be consistent whether you are writing Javascript or Python. Multi-line strings or expressions must also be consistently indented, not hanging like a bee hive at the end of the line. We just think the code looks a lot more stable that way.
39 |
40 | #### What if my Pull Request is closed?
41 |
42 | Don't worry, fix the problem and re-open it!
43 |
44 | #### Why do we follow this policy?
45 |
46 | This is because ERPNext is at a stage where it is being used by thousands of companies and introducing breaking changes can be harmful for everyone. Also we do not want to stop the speed of contributions and the best way to encourage contributors is to give fast feedback.
--------------------------------------------------------------------------------
/docs/installation.md:
--------------------------------------------------------------------------------
1 | ### Requirements
2 |
3 | You will need a computer/server. Options include:
4 |
5 | - A Normal Computer/VPS/Baremetal Server: This is strongly recommended. Frappe/ERPNext installs properly and works well on these
6 | - A Raspberry Pi, SAN Appliance, Network Router, Gaming Console, etc.: Although you may be able to install Frappe/ERPNext on specialized hardware, it is unlikely to work well and will be difficult for us to support. Strongly consider using a normal computer/VPS/baremetal server instead. **We do not support specialized hardware**.
7 | - A Toaster, Car, Firearm, Thermostat, etc.: Yes, many modern devices now have embedded computing capability. We live in interesting times. However, you should not install Frappe/ERPNext on these devices. Instead, install it on a normal computer/VPS/baremetal server. **We do not support installing on noncomputing devices**.
8 |
9 | To install the Frappe/ERPNext server software, you will need an operating system on your normal computer which is not Windows. Note that the command line interface does work on Windows, and you can use Frappe/ERPNext from any operating system with a web browser. However, the server software does not run on Windows. It does run on other operating systems, so choose one of these instead:
10 |
11 | - Linux: Ubuntu, Debian, CentOS are the preferred distros and are tested. [Arch Linux](https://github.com/frappe/bench/wiki/Install-ERPNext-on-ArchLinux) can also be used
12 | - Mac OS X
13 |
14 | ### Manual Install
15 |
16 | To manually install frappe/erpnext, you can follow this [this wiki](https://github.com/frappe/frappe/wiki/The-Hitchhiker%27s-Guide-to-Installing-Frappe-on-Linux) for Linux and [this wiki](https://github.com/frappe/frappe/wiki/The-Hitchhiker's-Guide-to-Installing-Frappe-on-Mac-OS-X) for MacOS. It gives an excellent explanation about the stack. You can also follow the steps mentioned below:
17 |
18 | #### 1. Install Prerequisites
19 |