2
0
mirror of https://github.com/frappe/bench.git synced 2024-11-12 00:06:36 +00:00

Merge pull request #955 from gavindsouza/sec-patch

fix: remove bench and supervisor from sudoers
This commit is contained in:
Chinmay Pai 2020-03-26 14:36:12 +05:30 committed by GitHub
commit c600c3f86b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 225 additions and 123 deletions

View File

@ -8,6 +8,7 @@ from bench.commands import bench_command
logger = logging.getLogger('bench') logger = logging.getLogger('bench')
from_command_line = False from_command_line = False
change_uid_msg = "You should not run this command as root"
def cli(): def cli():
global from_command_line global from_command_line
@ -48,7 +49,7 @@ def check_uid():
sys.exit(1) sys.exit(1)
def cmd_requires_root(): def cmd_requires_root():
if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers', 'lets-encrypt', 'fonts', if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers', 'supervisor', 'lets-encrypt', 'fonts',
'print', 'firewall', 'ssh-port', 'role', 'fail2ban', 'wildcard-ssl'): 'print', 'firewall', 'ssh-port', 'role', 'fail2ban', 'wildcard-ssl'):
return True return True
if len(sys.argv) >= 2 and sys.argv[1] in ('patch', 'renew-lets-encrypt', 'disable-production', if len(sys.argv) >= 2 and sys.argv[1] in ('patch', 'renew-lets-encrypt', 'disable-production',
@ -72,7 +73,7 @@ def change_uid():
drop_privileges(uid_name=frappe_user, gid_name=frappe_user) drop_privileges(uid_name=frappe_user, gid_name=frappe_user)
os.environ['HOME'] = pwd.getpwnam(frappe_user).pw_dir os.environ['HOME'] = pwd.getpwnam(frappe_user).pw_dir
else: else:
log('You should not run this command as root', level=3) log(change_uid_msg, level=3)
sys.exit(1) sys.exit(1)
def old_frappe_cli(bench_path='.'): def old_frappe_cli(bench_path='.'):

View File

@ -2,12 +2,21 @@
import os import os
import sys import sys
# imports - module imports
from bench.utils import exec_cmd
# imports - third party imports # imports - third party imports
from six import PY3
import click import click
from six import PY3
# imports - module imports
import bench.config.lets_encrypt
import bench.config.nginx
import bench.config.procfile
import bench.config.production_setup
import bench.config.redis
import bench.config.site_config
import bench.config.supervisor
import bench.utils
from bench.utils import exec_cmd, run_playbook
@click.group(help="Setup command group for enabling setting up a Frappe environment") @click.group(help="Setup command group for enabling setting up a Frappe environment")
@ -18,80 +27,59 @@ def setup():
@click.command("sudoers", help="Add commands to sudoers list for execution without password") @click.command("sudoers", help="Add commands to sudoers list for execution without password")
@click.argument("user") @click.argument("user")
def setup_sudoers(user): def setup_sudoers(user):
from bench.utils import setup_sudoers bench.utils.setup_sudoers(user)
setup_sudoers(user)
@click.command("nginx", help="Generate configuration files for NGINX") @click.command("nginx", help="Generate configuration files for NGINX")
@click.option("--yes", help="Yes to regeneration of nginx config file", default=False, is_flag=True) @click.option("--yes", help="Yes to regeneration of nginx config file", default=False, is_flag=True)
def setup_nginx(yes=False): def setup_nginx(yes=False):
from bench.config.nginx import make_nginx_conf bench.config.nginx.make_nginx_conf(bench_path=".", yes=yes)
make_nginx_conf(bench_path=".", yes=yes)
@click.command("reload-nginx", help="Checks NGINX config file and reloads service") @click.command("reload-nginx", help="Checks NGINX config file and reloads service")
def reload_nginx(): def reload_nginx():
from bench.config.production_setup import reload_nginx bench.config.production_setup.reload_nginx()
reload_nginx()
@click.command("supervisor", help="Generate configuration for supervisor") @click.command("supervisor", help="Generate configuration for supervisor")
@click.option("--user", help="optional user argument") @click.option("--user", help="optional user argument")
@click.option("--yes", help="Yes to regeneration of supervisor config", is_flag=True, default=False) @click.option("--yes", help="Yes to regeneration of supervisor config", is_flag=True, default=False)
def setup_supervisor(user=None, yes=False): def setup_supervisor(user=None, yes=False):
from bench.config.supervisor import generate_supervisor_config bench.config.supervisor.generate_supervisor_config(bench_path=".", user=user, yes=yes)
generate_supervisor_config(bench_path=".", user=user, yes=yes)
@click.command("redis", help="Generates configuration for Redis") @click.command("redis", help="Generates configuration for Redis")
def setup_redis(): def setup_redis():
from bench.config.redis import generate_config bench.config.redis.generate_config(".")
generate_config(".")
@click.command("fonts", help="Add Frappe fonts to system") @click.command("fonts", help="Add Frappe fonts to system")
def setup_fonts(): def setup_fonts():
from bench.utils import setup_fonts bench.utils.setup_fonts()
setup_fonts()
@click.command("production", help="Setup Frappe production environment for specific user") @click.command("production", help="Setup Frappe production environment for specific user")
@click.argument("user") @click.argument("user")
@click.option("--yes", help="Yes to regeneration config", is_flag=True, default=False) @click.option("--yes", help="Yes to regeneration config", is_flag=True, default=False)
def setup_production(user, yes=False): def setup_production(user, yes=False):
from bench.config.production_setup import setup_production bench.config.production_setup.setup_production(user=user, yes=yes)
# Install prereqs for production
from distutils.spawn import find_executable
if not find_executable("ansible"):
exec_cmd("sudo -H {0} -m pip install ansible".format(sys.executable))
if not find_executable("fail2ban-client"):
exec_cmd("bench setup role fail2ban")
if not find_executable("nginx"):
exec_cmd("bench setup role nginx")
if not find_executable("supervisord"):
exec_cmd("bench setup role supervisor")
setup_production(user=user, yes=yes)
@click.command("backups", help="Add cronjob for bench backups") @click.command("backups", help="Add cronjob for bench backups")
def setup_backups(): def setup_backups():
from bench.utils import setup_backups bench.utils.setup_backups()
setup_backups()
@click.command("env", help="Setup virtualenv for bench") @click.command("env", help="Setup virtualenv for bench")
@click.option("--python", type = str, default = "python3", help = "Path to Python Executable.") @click.option("--python", type = str, default = "python3", help = "Path to Python Executable.")
def setup_env(python="python3"): def setup_env(python="python3"):
from bench.utils import setup_env bench.utils.setup_env(python=python)
setup_env(python=python)
@click.command("firewall", help="Setup firewall for system") @click.command("firewall", help="Setup firewall for system")
@click.option("--ssh_port") @click.option("--ssh_port")
@click.option("--force") @click.option("--force")
def setup_firewall(ssh_port=None, force=False): def setup_firewall(ssh_port=None, force=False):
from bench.utils import run_playbook
if not force: if not force:
click.confirm("Setting up the firewall will block all ports except 80, 443 and {0}\nDo you want to continue?".format(ssh_port), abort=True) click.confirm("Setting up the firewall will block all ports except 80, 443 and {0}\nDo you want to continue?".format(ssh_port), abort=True)
@ -105,8 +93,6 @@ def setup_firewall(ssh_port=None, force=False):
@click.argument("port") @click.argument("port")
@click.option("--force") @click.option("--force")
def set_ssh_port(port, force=False): def set_ssh_port(port, force=False):
from bench.utils import run_playbook
if not force: if not force:
click.confirm("This will change your SSH Port to {}\nDo you want to continue?".format(port), abort=True) click.confirm("This will change your SSH Port to {}\nDo you want to continue?".format(port), abort=True)
@ -118,8 +104,7 @@ def set_ssh_port(port, force=False):
@click.option("--custom-domain") @click.option("--custom-domain")
@click.option('-n', '--non-interactive', default=False, is_flag=True, help="Run command non-interactively. This flag restarts nginx and runs certbot non interactively. Shouldn't be used on 1'st attempt") @click.option('-n', '--non-interactive', default=False, is_flag=True, help="Run command non-interactively. This flag restarts nginx and runs certbot non interactively. Shouldn't be used on 1'st attempt")
def setup_letsencrypt(site, custom_domain, non_interactive): def setup_letsencrypt(site, custom_domain, non_interactive):
from bench.config.lets_encrypt import setup_letsencrypt bench.config.lets_encrypt.setup_letsencrypt(site, custom_domain, bench_path=".", interactive=not non_interactive)
setup_letsencrypt(site, custom_domain, bench_path=".", interactive=not non_interactive)
@click.command("wildcard-ssl", help="Setup wildcard SSL certificate for multi-tenant bench") @click.command("wildcard-ssl", help="Setup wildcard SSL certificate for multi-tenant bench")
@ -127,20 +112,17 @@ def setup_letsencrypt(site, custom_domain, non_interactive):
@click.option("--email") @click.option("--email")
@click.option("--exclude-base-domain", default=False, is_flag=True, help="SSL Certificate not applicable for base domain") @click.option("--exclude-base-domain", default=False, is_flag=True, help="SSL Certificate not applicable for base domain")
def setup_wildcard_ssl(domain, email, exclude_base_domain): def setup_wildcard_ssl(domain, email, exclude_base_domain):
from bench.config.lets_encrypt import setup_wildcard_ssl bench.config.lets_encrypt.setup_wildcard_ssl(domain, email, bench_path=".", exclude_base_domain=exclude_base_domain)
setup_wildcard_ssl(domain, email, bench_path=".", exclude_base_domain=exclude_base_domain)
@click.command("procfile", help="Generate Procfile for bench start") @click.command("procfile", help="Generate Procfile for bench start")
def setup_procfile(): def setup_procfile():
from bench.config.procfile import setup_procfile bench.config.procfile.setup_procfile(".")
setup_procfile(".")
@click.command("socketio", help="Setup node dependencies for socketio server") @click.command("socketio", help="Setup node dependencies for socketio server")
def setup_socketio(): def setup_socketio():
from bench.utils import setup_socketio bench.utils.setup_socketio()
setup_socketio()
@click.command("requirements", help="Setup Python and Node dependencies") @click.command("requirements", help="Setup Python and Node dependencies")
@ -213,34 +195,28 @@ def setup_config():
@click.option("--ssl-certificate-key", help="Absolute path to SSL Certificate Key") @click.option("--ssl-certificate-key", help="Absolute path to SSL Certificate Key")
def add_domain(domain, site=None, ssl_certificate=None, ssl_certificate_key=None): def add_domain(domain, site=None, ssl_certificate=None, ssl_certificate_key=None):
"""Add custom domain to site""" """Add custom domain to site"""
from bench.config.site_config import add_domain
if not site: if not site:
print("Please specify site") print("Please specify site")
sys.exit(1) sys.exit(1)
add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path=".") bench.config.site_config.add_domain(site, domain, ssl_certificate, ssl_certificate_key, bench_path=".")
@click.command("remove-domain", help="Remove custom domain from a site") @click.command("remove-domain", help="Remove custom domain from a site")
@click.argument("domain") @click.argument("domain")
@click.option("--site", prompt=True) @click.option("--site", prompt=True)
def remove_domain(domain, site=None): def remove_domain(domain, site=None):
from bench.config.site_config import remove_domain
if not site: if not site:
print("Please specify site") print("Please specify site")
sys.exit(1) sys.exit(1)
remove_domain(site, domain, bench_path=".") bench.config.site_config.remove_domain(site, domain, bench_path=".")
@click.command("sync-domains", help="Check if there is a change in domains. If yes, updates the domains list.") @click.command("sync-domains", help="Check if there is a change in domains. If yes, updates the domains list.")
@click.option("--domain", multiple=True) @click.option("--domain", multiple=True)
@click.option("--site", prompt=True) @click.option("--site", prompt=True)
def sync_domains(domain=None, site=None): def sync_domains(domain=None, site=None):
from bench.config.site_config import sync_domains
if not site: if not site:
print("Please specify site") print("Please specify site")
sys.exit(1) sys.exit(1)
@ -251,7 +227,7 @@ def sync_domains(domain=None, site=None):
print("Domains should be a json list of strings or dictionaries") print("Domains should be a json list of strings or dictionaries")
sys.exit(1) sys.exit(1)
changed = sync_domains(site, domains, bench_path=".") changed = bench.config.site_config.sync_domains(site, domains, bench_path=".")
# if changed, success, else failure # if changed, success, else failure
sys.exit(0 if changed else 1) sys.exit(0 if changed else 1)
@ -263,8 +239,6 @@ def sync_domains(domain=None, site=None):
@click.option("--mysql_root_password") @click.option("--mysql_root_password")
@click.option("--container", is_flag=True, default=False) @click.option("--container", is_flag=True, default=False)
def setup_roles(role, **kwargs): def setup_roles(role, **kwargs):
from bench.utils import run_playbook
extra_vars = {"production": True} extra_vars = {"production": True}
extra_vars.update(kwargs) extra_vars.update(kwargs)
@ -279,7 +253,6 @@ def setup_roles(role, **kwargs):
@click.option("--bantime", default=600, help="The counter is set to zero if no match is found within 'findtime' seconds. Default is 600 seconds") @click.option("--bantime", default=600, help="The counter is set to zero if no match is found within 'findtime' seconds. Default is 600 seconds")
@click.option("--findtime", default=600, help="Duration (in seconds) for IP to be banned for. Negative number for 'permanent' ban. Default is 600 seconds") @click.option("--findtime", default=600, help="Duration (in seconds) for IP to be banned for. Negative number for 'permanent' ban. Default is 600 seconds")
def setup_nginx_proxy_jail(**kwargs): def setup_nginx_proxy_jail(**kwargs):
from bench.utils import run_playbook
run_playbook("roles/fail2ban/tasks/configure_nginx_jail.yml", extra_vars=kwargs) run_playbook("roles/fail2ban/tasks/configure_nginx_jail.yml", extra_vars=kwargs)

View File

@ -1,11 +1,30 @@
from bench.utils import get_program, exec_cmd, get_cmd_output, fix_prod_setup_perms, get_bench_name, find_executable, CommandFailedError # imports - standard imports
import os
import sys
from distutils.spawn import find_executable
# imports - module imports
from bench.config.common_site_config import get_config
from bench.config.nginx import make_nginx_conf
from bench.config.supervisor import generate_supervisor_config from bench.config.supervisor import generate_supervisor_config
from bench.config.systemd import generate_systemd_config from bench.config.systemd import generate_systemd_config
from bench.config.nginx import make_nginx_conf from bench.utils import CommandFailedError, exec_cmd, fix_prod_setup_perms, get_bench_name, get_cmd_output
from bench.config.common_site_config import get_config
import os, subprocess
def setup_production_prerequisites():
"""Installs ansible, fail2banc, NGINX and supervisor"""
if not find_executable("ansible"):
exec_cmd("sudo {0} -m pip install ansible".format(sys.executable))
if not find_executable("fail2ban-client"):
exec_cmd("bench setup role fail2ban")
if not find_executable("nginx"):
exec_cmd("bench setup role nginx")
if not find_executable("supervisord"):
exec_cmd("bench setup role supervisor")
def setup_production(user, bench_path='.', yes=False): def setup_production(user, bench_path='.', yes=False):
setup_production_prerequisites()
if get_config(bench_path).get('restart_supervisor_on_update') and get_config(bench_path).get('restart_systemd_on_update'): if get_config(bench_path).get('restart_supervisor_on_update') and get_config(bench_path).get('restart_systemd_on_update'):
raise Exception("You cannot use supervisor and systemd at the same time. Modify your common_site_config accordingly." ) raise Exception("You cannot use supervisor and systemd at the same time. Modify your common_site_config accordingly." )
@ -40,6 +59,7 @@ def setup_production(user, bench_path='.', yes=False):
reload_nginx() reload_nginx()
def disable_production(bench_path='.'): def disable_production(bench_path='.'):
bench_name = get_bench_name(bench_path) bench_name = get_bench_name(bench_path)
@ -62,28 +82,35 @@ def disable_production(bench_path='.'):
reload_nginx() reload_nginx()
def service(service, option):
if os.path.basename(get_program(['systemctl']) or '') == 'systemctl' and is_running_systemd(): def service(service_name, service_option):
exec_cmd("sudo {service_manager} {option} {service}".format(service_manager='systemctl', option=option, service=service)) if os.path.basename(find_executable('systemctl') or '') == 'systemctl' and is_running_systemd():
elif os.path.basename(get_program(['service']) or '') == 'service': systemctl_cmd = "sudo {service_manager} {service_option} {service_name}"
exec_cmd("sudo {service_manager} {service} {option} ".format(service_manager='service', service=service, option=option)) exec_cmd(systemctl_cmd.format(service_manager='systemctl', service_option=service_option, service_name=service_name))
elif os.path.basename(find_executable('service') or '') == 'service':
service_cmd = "sudo {service_manager} {service_name} {service_option}"
exec_cmd(service_cmd.format(service_manager='service', service_name=service_name, service_option=service_option))
else: else:
# look for 'service_manager' and 'service_manager_command' in environment # look for 'service_manager' and 'service_manager_command' in environment
service_manager = os.environ.get("BENCH_SERVICE_MANAGER") service_manager = os.environ.get("BENCH_SERVICE_MANAGER")
if service_manager: if service_manager:
service_manager_command = (os.environ.get("BENCH_SERVICE_MANAGER_COMMAND") service_manager_command = (os.environ.get("BENCH_SERVICE_MANAGER_COMMAND")
or "{service_manager} {option} {service}").format(service_manager=service_manager, service=service, option=option) or "{service_manager} {service_option} {service}").format(service_manager=service_manager, service=service, service_option=service_option)
exec_cmd(service_manager_command) exec_cmd(service_manager_command)
else: else:
raise Exception('No service manager found') raise Exception('No service manager found')
def get_supervisor_confdir(): def get_supervisor_confdir():
possiblities = ('/etc/supervisor/conf.d', '/etc/supervisor.d/', '/etc/supervisord/conf.d', '/etc/supervisord.d') possiblities = ('/etc/supervisor/conf.d', '/etc/supervisor.d/', '/etc/supervisord/conf.d', '/etc/supervisord.d')
for possiblity in possiblities: for possiblity in possiblities:
if os.path.exists(possiblity): if os.path.exists(possiblity):
return possiblity return possiblity
def remove_default_nginx_configs(): def remove_default_nginx_configs():
default_nginx_configs = ['/etc/nginx/conf.d/default.conf', '/etc/nginx/sites-enabled/default'] default_nginx_configs = ['/etc/nginx/conf.d/default.conf', '/etc/nginx/sites-enabled/default']
@ -95,6 +122,7 @@ def remove_default_nginx_configs():
def is_centos7(): def is_centos7():
return os.path.exists('/etc/redhat-release') and get_cmd_output("cat /etc/redhat-release | sed 's/Linux\ //g' | cut -d' ' -f3 | cut -d. -f1").strip() == '7' return os.path.exists('/etc/redhat-release') and get_cmd_output("cat /etc/redhat-release | sed 's/Linux\ //g' | cut -d' ' -f3 | cut -d. -f1").strip() == '7'
def is_running_systemd(): def is_running_systemd():
with open('/proc/1/comm') as f: with open('/proc/1/comm') as f:
comm = f.read().strip() comm = f.read().strip()
@ -104,20 +132,21 @@ def is_running_systemd():
return True return True
return False return False
def reload_supervisor(): def reload_supervisor():
supervisorctl = find_executable('supervisorctl') supervisorctl = find_executable('supervisorctl')
try: try:
# first try reread/update # first try reread/update
exec_cmd('sudo {0} reread'.format(supervisorctl)) exec_cmd('{0} reread'.format(supervisorctl))
exec_cmd('sudo {0} update'.format(supervisorctl)) exec_cmd('{0} update'.format(supervisorctl))
return return
except CommandFailedError: except CommandFailedError:
pass pass
try: try:
# something is wrong, so try reloading # something is wrong, so try reloading
exec_cmd('sudo {0} reload'.format(supervisorctl)) exec_cmd('{0} reload'.format(supervisorctl))
return return
except CommandFailedError: except CommandFailedError:
pass pass
@ -138,7 +167,7 @@ def reload_supervisor():
def reload_nginx(): def reload_nginx():
try: try:
subprocess.check_output(['sudo', find_executable('nginx'), '-t']) exec_cmd('sudo {0} -t'.format(find_executable('nginx')))
except: except:
raise raise

View File

@ -1,17 +1,27 @@
import os, getpass, click # imports - standard imports
import getpass
import os
# imports - module imports
import bench import bench
from bench.app import get_current_frappe_version, use_rq
from bench.utils import get_bench_name, find_executable
from bench.config.common_site_config import get_config, update_config, get_gunicorn_workers
# imports - third party imports
import click
from six.moves import configparser
def generate_supervisor_config(bench_path, user=None, yes=False): def generate_supervisor_config(bench_path, user=None, yes=False):
from bench.app import get_current_frappe_version, use_rq """Generate supervisor config for respective bench path"""
from bench.utils import get_bench_name, find_executable
from bench.config.common_site_config import get_config, update_config, get_gunicorn_workers
template = bench.env.get_template('supervisor.conf')
if not user: if not user:
user = getpass.getuser() user = getpass.getuser()
config = get_config(bench_path=bench_path) update_supervisord_conf(user=user)
template = bench.env.get_template('supervisor.conf')
config = get_config(bench_path=bench_path)
bench_dir = os.path.abspath(bench_path) bench_dir = os.path.abspath(bench_path)
config = template.render(**{ config = template.render(**{
@ -44,3 +54,37 @@ def generate_supervisor_config(bench_path, user=None, yes=False):
update_config({'restart_supervisor_on_update': True}, bench_path=bench_path) update_config({'restart_supervisor_on_update': True}, bench_path=bench_path)
update_config({'restart_systemd_on_update': False}, bench_path=bench_path) update_config({'restart_systemd_on_update': False}, bench_path=bench_path)
def get_supervisord_conf():
"""Returns path of supervisord config from possible paths"""
possibilities = ("supervisord.conf", "etc/supervisord.conf", "/etc/supervisord.conf", "/etc/supervisor/supervisord.conf", "/etc/supervisord.conf")
for possibility in possibilities:
if os.path.exists(possibility):
return possibility
def update_supervisord_conf(user):
"""From bench v5.0, we're moving to supervisor running as user"""
from bench.config.production_setup import service
supervisord_conf = get_supervisord_conf()
section = "unix_http_server"
if not supervisord_conf:
return
config = configparser.ConfigParser()
config.read(supervisord_conf)
if section not in config.sections():
config.add_section(section)
config.set(section, "chmod", "0760")
config.set(section, "chown", "{user}:{user}".format(user=user))
with open(supervisord_conf, "w") as f:
config.write(f)
# restart supervisor to take new changes into effect
service('supervisor', 'restart')

View File

@ -1,20 +1,20 @@
# This file is auto-generated by frappe/bench
# To re-generate this file, run "bench setup sudoers"
{% if service %} {% if service %}
{{ user }} ALL = (root) {{ service }} {{ user }} ALL = (root) {{ service }}
{{ user }} ALL = (root) NOPASSWD: {{ service }} nginx * {{ user }} ALL = (root) NOPASSWD: {{ service }} nginx *
{{ user }} ALL = (root) NOPASSWD: {{ service }} supervisord *
{% endif %} {% endif %}
{% if systemctl %} {% if systemctl %}
{{ user }} ALL = (root) {{ systemctl }} {{ user }} ALL = (root) {{ systemctl }}
{{ user }} ALL = (root) NOPASSWD: {{ systemctl }} * nginx {{ user }} ALL = (root) NOPASSWD: {{ systemctl }} * nginx
{{ user }} ALL = (root) NOPASSWD: {{ systemctl }} * supervisord
{% endif %}
{% if supervisorctl %}
{{ user }} ALL = (root) NOPASSWD: {{ supervisorctl }}
{% endif %} {% endif %}
{% if nginx %} {% if nginx %}
{{ user }} ALL = (root) NOPASSWD: {{ nginx }} {{ user }} ALL = (root) NOPASSWD: {{ nginx }}
{% endif %} {% endif %}
{{ user }} ALL = (root) NOPASSWD: /opt/certbot-auto {{ user }} ALL = (root) NOPASSWD: /opt/certbot-auto
{{ user }} ALL = (root) NOPASSWD: {{ bench }}
Defaults:{{ user }} !requiretty Defaults:{{ user }} !requiretty

View File

@ -4,3 +4,4 @@ bench.patches.v3.redis_bind_ip
bench.patches.v4.update_node bench.patches.v4.update_node
bench.patches.v4.update_socketio bench.patches.v4.update_socketio
bench.patches.v4.install_yarn #2 bench.patches.v4.install_yarn #2
bench.patches.v5.fix_user_permissions

View File

View File

@ -0,0 +1,59 @@
# imports - standard imports
import getpass
import os
import subprocess
# imports - module imports
from bench.cli import change_uid_msg
from bench.config.production_setup import get_supervisor_confdir, is_centos7
from bench.config.common_site_config import get_config
from bench.utils import exec_cmd, get_bench_name, get_cmd_output
def is_sudoers_set():
"""Check if bench sudoers is set"""
cmd = ["sudo", "-n", "bench"]
with open(os.devnull, "wb") as f:
return_code_check = not subprocess.call(cmd, stdout=f)
if return_code_check:
try:
bench_warn = change_uid_msg in get_cmd_output(cmd, _raise=False)
except subprocess.CalledProcessError:
bench_warn = False
finally:
return_code_check = return_code_check and bench_warn
return return_code_check
def is_production_set(bench_path):
"""Check if production is set for current bench"""
production_setup = False
bench_name = get_bench_name(bench_path)
supervisor_conf_extn = "ini" if is_centos7() else "conf"
supervisor_conf_file_name = '{bench_name}.{extn}'.format(bench_name=bench_name, extn=supervisor_conf_extn)
supervisor_conf = os.path.join(get_supervisor_confdir(), supervisor_conf_file_name)
if os.path.exists(supervisor_conf):
production_setup = production_setup or True
nginx_conf = '/etc/nginx/conf.d/{bench_name}.conf'.format(bench_name=bench_name)
if os.path.exists(nginx_conf):
production_setup = production_setup or True
return production_setup
def execute(bench_path):
"""This patch checks if bench sudoers is set and regenerate supervisor and sudoers files"""
user = get_config('.').get("frappe_user") or getpass.getuser()
if is_sudoers_set():
exec_cmd("sudo bench setup sudoers {user}".format(user=user))
if is_production_set(bench_path):
exec_cmd("sudo bench setup supervisor --yes --user {user}".format(user=user))

View File

@ -61,6 +61,9 @@ class TestSetupProduction(TestBenchBase):
def assert_sudoers(self, user): def assert_sudoers(self, user):
sudoers_file = '/etc/sudoers.d/frappe' sudoers_file = '/etc/sudoers.d/frappe'
service = bench.utils.which("service")
nginx = bench.utils.which("nginx")
self.assertTrue(self.file_exists(sudoers_file)) self.assertTrue(self.file_exists(sudoers_file))
if os.environ.get("CI"): if os.environ.get("CI"):
@ -69,9 +72,8 @@ class TestSetupProduction(TestBenchBase):
with open(sudoers_file, 'r') as f: with open(sudoers_file, 'r') as f:
sudoers = f.read() sudoers = f.read()
self.assertTrue('{user} ALL = (root) NOPASSWD: /usr/sbin/service nginx *'.format(user=user) in sudoers) self.assertTrue('{user} ALL = (root) NOPASSWD: {service} nginx *'.format(service=service, user=user) in sudoers)
self.assertTrue('{user} ALL = (root) NOPASSWD: /usr/bin/supervisorctl'.format(user=user) in sudoers) self.assertTrue('{user} ALL = (root) NOPASSWD: {nginx}'.format(nginx=nginx, user=user) in sudoers)
self.assertTrue('{user} ALL = (root) NOPASSWD: /usr/sbin/nginx'.format(user=user) in sudoers)
def assert_supervisor_config(self, bench_name, use_rq=True): def assert_supervisor_config(self, bench_name, use_rq=True):
@ -126,12 +128,12 @@ class TestSetupProduction(TestBenchBase):
def assert_supervisor_process(self, bench_name, use_rq=True, disable_production=False): def assert_supervisor_process(self, bench_name, use_rq=True, disable_production=False):
out = bench.utils.get_cmd_output("sudo supervisorctl status") out = bench.utils.get_cmd_output("supervisorctl status")
while "STARTING" in out: while "STARTING" in out:
print ("Waiting for all processes to start...") print ("Waiting for all processes to start...")
time.sleep(10) time.sleep(10)
out = bench.utils.get_cmd_output("sudo supervisorctl status") out = bench.utils.get_cmd_output("supervisorctl status")
tests = [ tests = [
"{bench_name}-web:{bench_name}-frappe-web[\s]+RUNNING", "{bench_name}-web:{bench_name}-frappe-web[\s]+RUNNING",

View File

@ -37,6 +37,7 @@ class CommandFailedError(Exception):
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
folders_in_bench = ('apps', 'sites', 'config', 'logs', 'config/pids') folders_in_bench = ('apps', 'sites', 'config', 'logs', 'config/pids')
sudoers_file = '/etc/sudoers.d/frappe'
class color: class color:
@ -65,10 +66,10 @@ def log(message, level=0):
2: color.red + 'ERROR', # fail 2: color.red + 'ERROR', # fail
3: color.yellow + 'WARN' # warn/suggest 3: color.yellow + 'WARN' # warn/suggest
} }
start = (levels.get(level) + ': ') if level in levels else '' start_line = (levels.get(level) + ': ') if level in levels else ''
end = '\033[0m' end_line = '\033[0m'
print(start + message + end) print(start_line + message + end_line)
def safe_decode(string, encoding = 'utf-8'): def safe_decode(string, encoding = 'utf-8'):
@ -393,16 +394,12 @@ def setup_sudoers(user):
if set_permissions: if set_permissions:
os.chmod('/etc/sudoers', 0o440) os.chmod('/etc/sudoers', 0o440)
sudoers_file = '/etc/sudoers.d/frappe'
template = env.get_template('frappe_sudoers') template = env.get_template('frappe_sudoers')
frappe_sudoers = template.render(**{ frappe_sudoers = template.render(**{
'user': user, 'user': user,
'service': find_executable('service'), 'service': find_executable('service'),
'systemctl': find_executable('systemctl'), 'systemctl': find_executable('systemctl'),
'supervisorctl': find_executable('supervisorctl'),
'nginx': find_executable('nginx'), 'nginx': find_executable('nginx'),
'bench': find_executable('bench')
}) })
frappe_sudoers = safe_decode(frappe_sudoers) frappe_sudoers = safe_decode(frappe_sudoers)
@ -410,6 +407,7 @@ def setup_sudoers(user):
f.write(frappe_sudoers) f.write(frappe_sudoers)
os.chmod(sudoers_file, 0o440) os.chmod(sudoers_file, 0o440)
log("Sudoers was set up for user {}".format(user), level=1)
def setup_logging(bench_path='.'): def setup_logging(bench_path='.'):
@ -423,17 +421,11 @@ def setup_logging(bench_path='.'):
logger.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
def get_program(programs):
program = None
for p in programs:
program = find_executable(p)
if program:
break
return program
def get_process_manager(): def get_process_manager():
return get_program(['foreman', 'forego', 'honcho']) for proc_man in ['honcho', 'foreman', 'forego']:
proc_man_path = find_executable(proc_man)
if proc_man_path:
return proc_man_path
def start(no_dev=False, concurrency=None, procfile=None): def start(no_dev=False, concurrency=None, procfile=None):
@ -488,15 +480,16 @@ def check_git_for_shallow_clone():
return True return True
def get_cmd_output(cmd, cwd='.'): def get_cmd_output(cmd, cwd='.', _raise=True):
output = ""
try: try:
output = subprocess.check_output(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE).strip() output = subprocess.check_output(cmd, cwd=cwd, shell=True, stderr=subprocess.PIPE).strip()
output = output.decode('utf-8')
return output
except subprocess.CalledProcessError as e: except subprocess.CalledProcessError as e:
if e.output: if e.output:
print(e.output) output = e.output
raise elif _raise:
raise
return safe_decode(output)
def safe_encode(what, encoding = 'utf-8'): def safe_encode(what, encoding = 'utf-8'):
@ -518,7 +511,7 @@ def restart_supervisor_processes(bench_path='.', web_workers=False):
exec_cmd(cmd, cwd=bench_path) exec_cmd(cmd, cwd=bench_path)
else: else:
supervisor_status = subprocess.check_output(['sudo', 'supervisorctl', 'status'], cwd=bench_path) supervisor_status = get_cmd_output('supervisorctl status', cwd=bench_path)
supervisor_status = safe_decode(supervisor_status) supervisor_status = safe_decode(supervisor_status)
if web_workers and '{bench_name}-web:'.format(bench_name=bench_name) in supervisor_status: if web_workers and '{bench_name}-web:'.format(bench_name=bench_name) in supervisor_status:
@ -535,7 +528,7 @@ def restart_supervisor_processes(bench_path='.', web_workers=False):
else: else:
group = 'frappe:' group = 'frappe:'
exec_cmd('sudo supervisorctl restart {group}'.format(group=group), cwd=bench_path) exec_cmd('supervisorctl restart {group}'.format(group=group), cwd=bench_path)
def restart_systemd_processes(bench_path='.', web_workers=False): def restart_systemd_processes(bench_path='.', web_workers=False):
@ -1048,8 +1041,8 @@ def migrate_env(python, backup=False):
from bench.config.common_site_config import get_config from bench.config.common_site_config import get_config
from bench.app import get_apps from bench.app import get_apps
log = logging.getLogger(__name__) logger = logging.getLogger(__name__)
log.setLevel(logging.DEBUG) logger.setLevel(logging.DEBUG)
nvenv = 'env' nvenv = 'env'
path = os.getcwd() path = os.getcwd()
@ -1065,12 +1058,12 @@ def migrate_env(python, backup=False):
redis = '{redis} -p {port}'.format(redis=which('redis-cli'), port=rredis.port) redis = '{redis} -p {port}'.format(redis=which('redis-cli'), port=rredis.port)
log.debug('Clearing Redis Cache...') logger.debug('Clearing Redis Cache...')
exec_cmd('{redis} FLUSHALL'.format(redis = redis)) exec_cmd('{redis} FLUSHALL'.format(redis = redis))
log.debug('Clearing Redis DataBase...') logger.debug('Clearing Redis DataBase...')
exec_cmd('{redis} FLUSHDB'.format(redis = redis)) exec_cmd('{redis} FLUSHDB'.format(redis = redis))
except: except:
log.warn('Please ensure Redis Connections are running or Daemonized.') logger.warn('Please ensure Redis Connections are running or Daemonized.')
# Backup venv: restore using `virtualenv --relocatable` if needed # Backup venv: restore using `virtualenv --relocatable` if needed
if backup: if backup:
@ -1081,7 +1074,7 @@ def migrate_env(python, backup=False):
source = os.path.join(path, 'env') source = os.path.join(path, 'env')
target = parch target = parch
log.debug('Backing up Virtual Environment') logger.debug('Backing up Virtual Environment')
stamp = datetime.now().strftime('%Y%m%d_%H%M%S') stamp = datetime.now().strftime('%Y%m%d_%H%M%S')
dest = os.path.join(path, str(stamp)) dest = os.path.join(path, str(stamp))
@ -1090,15 +1083,15 @@ def migrate_env(python, backup=False):
# Create virtualenv using specified python # Create virtualenv using specified python
try: try:
log.debug('Setting up a New Virtual {} Environment'.format(python)) logger.debug('Setting up a New Virtual {} Environment'.format(python))
exec_cmd('{virtualenv} --python {python} {pvenv}'.format(virtualenv=virtualenv, python=python, pvenv=pvenv)) exec_cmd('{virtualenv} --python {python} {pvenv}'.format(virtualenv=virtualenv, python=python, pvenv=pvenv))
apps = ' '.join(["-e {}".format(os.path.join("apps", app)) for app in get_apps()]) apps = ' '.join(["-e {}".format(os.path.join("apps", app)) for app in get_apps()])
exec_cmd('{0} install -q -U {1}'.format(pip, apps)) exec_cmd('{0} install -q -U {1}'.format(pip, apps))
log.debug('Migration Successful to {}'.format(python)) logger.debug('Migration Successful to {}'.format(python))
except: except:
log.debug('Migration Error') logger.debug('Migration Error')
raise raise