Skip site navigation (1)Skip section navigation (2)
Date:      Fri, 31 Jul 2015 14:05:59 +0000 (UTC)
From:      Fukang Chen <loader@FreeBSD.org>
To:        ports-committers@freebsd.org, svn-ports-all@freebsd.org, svn-ports-head@freebsd.org
Subject:   svn commit: r393308 - in head/www: . py-frappe-bench py-frappe-bench/files
Message-ID:  <201507311405.t6VE5xVM047225@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: loader (doc committer)
Date: Fri Jul 31 14:05:58 2015
New Revision: 393308
URL: https://svnweb.freebsd.org/changeset/ports/393308

Log:
  [NEW] www/py-frappe-bench: Frappe / ERPNext apps setup tool
  
  The bench allows you to setup Frappe / ERPNext apps on
  your local machine or a production server. You can use
  the bench to serve multiple frappe sites.
  
  WWW: https://github.com/frappe/bench
  
  Approved by: koobs
  Differential Revision: https://reviews.freebsd.org/D3120

Added:
  head/www/py-frappe-bench/
  head/www/py-frappe-bench/Makefile   (contents, props changed)
  head/www/py-frappe-bench/distinfo   (contents, props changed)
  head/www/py-frappe-bench/files/
  head/www/py-frappe-bench/files/patch-a93acec   (contents, props changed)
  head/www/py-frappe-bench/files/patch-setup.py   (contents, props changed)
  head/www/py-frappe-bench/pkg-descr   (contents, props changed)
Modified:
  head/www/Makefile

Modified: head/www/Makefile
==============================================================================
--- head/www/Makefile	Fri Jul 31 13:57:21 2015	(r393307)
+++ head/www/Makefile	Fri Jul 31 14:05:58 2015	(r393308)
@@ -1586,6 +1586,7 @@
     SUBDIR += py-flup
     SUBDIR += py-formalchemy
     SUBDIR += py-formencode
+    SUBDIR += py-frappe-bench
     SUBDIR += py-frozen-flask
     SUBDIR += py-funkload
     SUBDIR += py-gandi.cli

Added: head/www/py-frappe-bench/Makefile
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/www/py-frappe-bench/Makefile	Fri Jul 31 14:05:58 2015	(r393308)
@@ -0,0 +1,33 @@
+# Created by: loader <loader@FreeBSD.org>
+# $FreeBSD$
+
+PORTNAME=	frappe-bench
+PORTVERSION=	0.92
+DISTVERSIONPREFIX=	v
+CATEGORIES=	www python
+PKGNAMEPREFIX=	${PYTHON_PKGNAMEPREFIX}
+
+MAINTAINER=	loader@FreeBSD.org
+COMMENT=	Frappe / ERPNext apps setup tool
+
+LICENSE=	GPLv3
+LICENSE_FILE=	${WRKSRC}/LICENSE.md
+
+RUN_DEPENDS=	${PYTHON_PKGNAMEPREFIX}click>0:${PORTSDIR}/devel/py-click \
+		${PYTHON_PKGNAMEPREFIX}Jinja2>0:${PORTSDIR}/devel/py-Jinja2 \
+		${PYTHON_PKGNAMEPREFIX}virtualenv>0:${PORTSDIR}/devel/py-virtualenv \
+		${PYTHON_PKGNAMEPREFIX}requests>0:${PORTSDIR}/www/py-requests \
+		${PYTHON_PKGNAMEPREFIX}honcho>0:${PORTSDIR}/sysutils/py-honcho \
+		${PYTHON_PKGNAMEPREFIX}semantic_version>0:${PORTSDIR}/devel/py-semantic_version \
+		${PYTHON_PKGNAMEPREFIX}GitPython>=1.0.1:${PORTSDIR}/devel/py-gitpython \
+		${PYTHON_PKGNAMEPREFIX}pip>0:${PORTSDIR}/devel/py-pip \
+		git:${PORTSDIR}/devel/git
+
+USE_GITHUB=	yes
+GH_ACCOUNT=	frappe
+GH_PROJECT=	bench
+
+USES=		python
+USE_PYTHON=	autoplist distutils concurrent
+
+.include <bsd.port.mk>

Added: head/www/py-frappe-bench/distinfo
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/www/py-frappe-bench/distinfo	Fri Jul 31 14:05:58 2015	(r393308)
@@ -0,0 +1,2 @@
+SHA256 (frappe-bench-v0.92_GH0.tar.gz) = 4095b752bd777166d48054df6fb198ad1349e0bdf60d112e632ea622a9b99587
+SIZE (frappe-bench-v0.92_GH0.tar.gz) = 30835

Added: head/www/py-frappe-bench/files/patch-a93acec
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/www/py-frappe-bench/files/patch-a93acec	Fri Jul 31 14:05:58 2015	(r393308)
@@ -0,0 +1,1763 @@
+--- README.md.orig	2014-11-19 06:36:44 UTC
++++ README.md
+@@ -5,7 +5,7 @@ The bench allows you to setup Frappe / E
+ 
+ To do this install, you must have basic information on how Linux works and should be able to use the command-line. If you are looking easier ways to get started and evaluate ERPNext, [download the Virtual Machine or take a free trial at FrappeCloud.com](https://erpnext.com/use).
+ 
+-For questions, please join the [developer forum](https://groups.google.com/group/erpnext-developer-forum).
++For questions, please join the [developer forum](https://discuss.frappe.io/).
+ 
+ Installation
+ ============
+@@ -37,6 +37,15 @@ Install pre-requisites,
+ * Redis
+ * [wkhtmltopdf](http://wkhtmltopdf.org/downloads.html) (optional, required for pdf generation)
+ * Memcached
++
++For installing MaraiDB on OSX, use:
++```
++brew install mariadb
++mysql_install_db
++mysql.server start
++mysqladmin -uroot password ROOTPASSWORD
++```
++
+ 	
+ Install bench as a *non root* user,
+ 
+@@ -105,11 +114,9 @@ To setup a bench that runs ERPNext, run 
+ cd ~
+ bench init frappe-bench
+ cd frappe-bench
+-bench get-app erpnext https://github.com/frappe/erpnext				# Add ERPNext to your bench apps
+-bench get-app shopping_cart https://github.com/frappe/shopping-cart	# Add Shopping cart to your bench apps
+-bench new-site site1.local											# Create a new site
+-bench frappe --install_app erpnext site1.local						# Install ERPNext for the site
+-bench frappe --install_app shopping_cart site1.local				# Install Shopping cart for the site
++bench get-app erpnext https://github.com/frappe/erpnext			# Add ERPNext to your bench apps
++bench new-site site1.local						# Create a new site
++bench install-app erpnext						# Install ERPNext for the site
+ ```
+ 
+ You can now either use `bench start` or setup the bench for production use.
+@@ -162,7 +169,7 @@ Frappe Processes
+ * WSGI Server
+ 
+ 	* The WSGI server is responsible for responding to the HTTP requests to
+-	frappe. In development scenario (`frappe --serve` or `bench start`), the
++	frappe. In development scenario (`bench serve` or `bench start`), the
+ 	Werkzeug WSGI server is used and in production, gunicorn (automatically
+ 	configured in supervisor) is used.
+ 
+--- bench/app.py.orig	2014-11-19 06:36:44 UTC
++++ bench/app.py
+@@ -1,12 +1,22 @@
+ import os
+-from .utils import exec_cmd, get_frappe, check_git_for_shallow_clone, get_config, build_assets, restart_supervisor_processes, get_cmd_output
++from .utils import exec_cmd, get_frappe, check_git_for_shallow_clone, get_config, build_assets, restart_supervisor_processes, get_cmd_output, run_frappe_cmd
+ 
+ import logging
+ import requests
++import semantic_version
+ import json
++import re
++import subprocess
++
+ 
+ logger = logging.getLogger(__name__)
+ 
++class MajorVersionUpgradeException(Exception):
++	def __init__(self, message, upstream_version, local_version):
++		super(MajorVersionUpgradeException, self).__init__(message)
++		self.upstream_version = upstream_version
++		self.local_version = local_version
++
+ def get_apps(bench='.'):
+ 	try:
+ 		with open(os.path.join(bench, 'sites', 'apps.txt')) as f:
+@@ -18,10 +28,19 @@ def add_to_appstxt(app, bench='.'):
+ 	apps = get_apps(bench=bench)
+ 	if app not in apps:
+ 		apps.append(app)
+-		with open(os.path.join(bench, 'sites', 'apps.txt'), 'w') as f:
+-			return f.write('\n'.join(apps))
++		return write_appstxt(apps, bench=bench)
+ 
+-def get_app(app, git_url, branch=None, bench='.'):
++def remove_from_appstxt(app, bench='.'):
++	apps = get_apps(bench=bench)
++	if app in apps:
++		apps.remove(app)
++		return write_appstxt(apps, bench=bench)
++
++def write_appstxt(apps, bench='.'):
++	with open(os.path.join(bench, 'sites', 'apps.txt'), 'w') as f:
++		return f.write('\n'.join(apps))
++
++def get_app(app, git_url, branch=None, bench='.', build_asset_files=True):
+ 	logger.info('getting app {}'.format(app))
+ 	shallow_clone = '--depth 1' if check_git_for_shallow_clone() and get_config().get('shallow_clone') else ''
+ 	branch = '--branch {branch}'.format(branch=branch) if branch else ''
+@@ -33,14 +52,20 @@ def get_app(app, git_url, branch=None, b
+ 			cwd=os.path.join(bench, 'apps'))
+ 	print 'installing', app
+ 	install_app(app, bench=bench)
+-	build_assets(bench=bench)
++	if build_asset_files:
++		build_assets(bench=bench)
+ 	conf = get_config()
+ 	if conf.get('restart_supervisor_on_update'):
+ 		restart_supervisor_processes(bench=bench)
+ 
+ def new_app(app, bench='.'):
+ 	logger.info('creating new app {}'.format(app))
+-	exec_cmd("{frappe} --make_app {apps}".format(frappe=get_frappe(bench=bench), apps=os.path.join(bench, 'apps')))
++	apps = os.path.abspath(os.path.join(bench, 'apps'))
++	if FRAPPE_VERSION == 4:
++		exec_cmd("{frappe} --make_app {apps} {app}".format(frappe=get_frappe(bench=bench),
++			apps=apps, app=app))
++	else:
++		run_frappe_cmd('make-app', apps, app, bench=bench)
+ 	install_app(app, bench=bench)
+ 
+ def install_app(app, bench='.'):
+@@ -57,19 +82,112 @@ def pull_all_apps(bench='.'):
+ 	apps_dir = os.path.join(bench, 'apps')
+ 	apps = [app for app in os.listdir(apps_dir) if os.path.isdir(os.path.join(apps_dir, app))]
+ 	rebase = '--rebase' if get_config().get('rebase_on_pull') else ''
++	frappe_dir = os.path.join(apps_dir, 'frappe')
++
+ 	for app in apps:
+ 		app_dir = os.path.join(apps_dir, app)
+ 		if os.path.exists(os.path.join(app_dir, '.git')):
+ 			logger.info('pulling {0}'.format(app))
+ 			exec_cmd("git pull {rebase} upstream {branch}".format(rebase=rebase, branch=get_current_branch(app_dir)), cwd=app_dir)
+ 
++def is_version_upgrade(bench='.', branch=None):
++	apps_dir = os.path.join(bench, 'apps')
++	frappe_dir = os.path.join(apps_dir, 'frappe')
++
++	fetch_upstream(frappe_dir)
++	upstream_version = get_upstream_version(frappe_dir, branch=branch)
++
++	if not upstream_version:
++		raise Exception("Current branch of 'frappe' not in upstream")
++
++	local_version = get_major_version(get_current_version(frappe_dir))
++	upstream_version = get_major_version(upstream_version)
++
++	if upstream_version - local_version  > 0:
++		return (local_version, upstream_version)
++	return False
++
++def get_current_frappe_version(bench='.'):
++	apps_dir = os.path.join(bench, 'apps')
++	frappe_dir = os.path.join(apps_dir, 'frappe')
++
++	try:
++		return get_major_version(get_current_version(frappe_dir))
++	except IOError:
++		return ''
++
+ def get_current_branch(repo_dir):
+ 	return get_cmd_output("basename $(git symbolic-ref -q HEAD)", cwd=repo_dir)
+ 
++def fetch_upstream(repo_dir):
++	return exec_cmd("git fetch upstream", cwd=repo_dir)
++
++def get_current_version(repo_dir):
++	with open(os.path.join(repo_dir, 'setup.py')) as f:
++		return get_version_from_string(f.read())
++
++def get_upstream_version(repo_dir, branch=None):
++	if not branch:
++		branch = get_current_branch(repo_dir)
++	try:
++		contents = subprocess.check_output(['git', 'show', 'upstream/{branch}:setup.py'.format(branch=branch)], cwd=repo_dir, stderr=subprocess.STDOUT)
++	except subprocess.CalledProcessError, e:
++		if "Invalid object" in e.output:
++			return None
++		else:
++			raise
++	return get_version_from_string(contents)
++
++def switch_branch(branch, apps=None, bench='.', upgrade=False):
++	from .utils import update_requirements, backup_all_sites, patch_sites, build_assets, pre_upgrade, post_upgrade
++	import utils
++	apps_dir = os.path.join(bench, 'apps')
++	version_upgrade = is_version_upgrade(bench=bench, branch=branch)
++	if version_upgrade and not upgrade:
++		raise MajorVersionUpgradeException("Switching to {0} will cause upgrade from {1} to {2}. Pass --upgrade to confirm".format(branch, version_upgrade[0], version_upgrade[1]), version_upgrade[0], version_upgrade[1])
++
++	if not apps:
++	    apps = ('frappe', 'erpnext', 'shopping_cart')
++	for app in apps:
++		app_dir = os.path.join(apps_dir, app)
++		if os.path.exists(app_dir):
++			unshallow = "--unshallow" if os.path.exists(os.path.join(app_dir, ".git", "shallow")) else ""
++			exec_cmd("git config --unset-all remote.upstream.fetch", cwd=app_dir)
++			exec_cmd("git config --add remote.upstream.fetch '+refs/heads/*:refs/remotes/upstream/*'", cwd=app_dir)
++			exec_cmd("git fetch upstream {unshallow}".format(unshallow=unshallow), cwd=app_dir)
++			exec_cmd("git checkout {branch}".format(branch=branch), cwd=app_dir)
++			exec_cmd("git merge upstream/{branch}".format(branch=branch), cwd=app_dir)
++
++	if version_upgrade and upgrade:
++		update_requirements()
++		pre_upgrade(version_upgrade[0], version_upgrade[1])
++		reload(utils)
++		backup_all_sites()
++		patch_sites()
++		build_assets()
++		post_upgrade(version_upgrade[0], version_upgrade[1])
++
++def switch_to_master(apps=None, bench='.', upgrade=False):
++	switch_branch('master', apps=apps, bench=bench, upgrade=upgrade)
++
++def switch_to_develop(apps=None, bench='.', upgrade=False):
++	switch_branch('develop', apps=apps, bench=bench, upgrade=upgrade)
++
++def switch_to_v4(apps=None, bench='.', upgrade=False):
++	switch_branch('v4.x.x', apps=apps, bench=bench, upgrade=upgrade)
++
++def get_version_from_string(contents):
++	match = re.search(r"^(\s*%s\s*=\s*['\\\"])(.+?)(['\"])(?sm)" % 'version',
++			contents)
++	return match.group(2)
++
++def get_major_version(version):
++	return semantic_version.Version(version).major
++
+ def install_apps_from_path(path, bench='.'):
+ 	apps = get_apps_json(path)
+ 	for app in apps:
+-		get_app(app['name'], app['url'], branch=app.get('branch'), bench=bench)
++		get_app(app['name'], app['url'], branch=app.get('branch'), bench=bench, build_asset_files=False)
+ 
+ def get_apps_json(path):
+ 	if path.startswith('http'):
+@@ -78,3 +196,5 @@ def get_apps_json(path):
+ 	else:
+ 		with open(path) as f:
+ 			return json.load(f)
++
++FRAPPE_VERSION = get_current_frappe_version()
+--- bench/cli.py.orig	2014-11-19 06:36:44 UTC
++++ bench/cli.py
+@@ -8,32 +8,54 @@ from .utils import setup_sudoers as _set
+ from .utils import start as _start
+ from .utils import setup_procfile as _setup_procfile
+ from .utils import set_nginx_port as _set_nginx_port
+-from .utils import set_nginx_port as _set_nginx_port
++from .utils import set_url_root as _set_url_root
+ from .utils import set_default_site as _set_default_site
+-from .utils import (build_assets, patch_sites, exec_cmd, update_bench, get_frappe, setup_logging,
++from .utils import (build_assets, patch_sites, exec_cmd, update_bench, get_env_cmd, get_frappe, setup_logging,
+ 					get_config, update_config, restart_supervisor_processes, put_config, default_config, update_requirements,
+-					backup_all_sites, backup_site, get_sites, prime_wheel_cache, is_root, set_mariadb_host, drop_privileges)
++					backup_all_sites, backup_site, get_sites, prime_wheel_cache, is_root, set_mariadb_host, drop_privileges,
++					fix_file_perms, fix_prod_setup_perms, set_ssl_certificate, set_ssl_certificate_key, get_cmd_output, post_upgrade,
++					pre_upgrade, PatchError, download_translations_p)
+ from .app import get_app as _get_app
+ from .app import new_app as _new_app
+-from .app import pull_all_apps
+-from .config import generate_nginx_config, generate_supervisor_config
++from .app import pull_all_apps, get_apps, get_current_frappe_version, is_version_upgrade, switch_to_v4, switch_to_master, switch_to_develop
++from .config import generate_nginx_config, generate_supervisor_config, generate_redis_config
+ from .production_setup import setup_production as _setup_production
++from .migrate_to_v5 import migrate_to_v5
+ import os
+ import sys
+ import logging
+ import copy
++import json
+ import pwd
+ import grp
++import subprocess
+ 
+ logger = logging.getLogger('bench')
+ 
++global FRAPPE_VERSION
++
+ def cli():
+ 	check_uid()
+ 	change_dir()
+ 	change_uid()
+ 	if len(sys.argv) > 2 and sys.argv[1] == "frappe":
+-		return frappe()
+-	return bench()
++		return old_frappe_cli()
++	elif len(sys.argv) > 1 and sys.argv[1] in get_frappe_commands():
++		return frappe_cmd()
++	elif len(sys.argv) > 1 and sys.argv[1] in ("--site", "--verbose", "--force", "--profile"):
++		return frappe_cmd()
++	elif len(sys.argv) > 1 and sys.argv[1]=="--help":
++		print click.Context(bench).get_help()
++		print
++		print get_frappe_help()
++		return 
++	elif len(sys.argv) > 1 and sys.argv[1] in get_apps():
++		return app_cmd()
++	else:
++		try:
++			bench()
++		except PatchError:
++			sys.exit(1)
+ 
+ def cmd_requires_root():
+ 	if len(sys.argv) > 2 and sys.argv[2] in ('production', 'sudoers'):
+@@ -57,17 +79,51 @@ def change_uid():
+ 			sys.exit(1)
+ 
+ def change_dir():
++	if os.path.exists('config.json') or "init" in sys.argv:
++		return
+ 	dir_path_file = '/etc/frappe_bench_dir'
+ 	if os.path.exists(dir_path_file):
+ 		with open(dir_path_file) as f:
+ 			dir_path = f.read().strip()
+-		os.chdir(dir_path)
++		if os.path.exists(dir_path):
++			os.chdir(dir_path)
+ 
+-def frappe(bench='.'):
++def old_frappe_cli(bench='.'):
+ 	f = get_frappe(bench=bench)
+ 	os.chdir(os.path.join(bench, 'sites'))
+ 	os.execv(f, [f] + sys.argv[2:])
+ 
++def app_cmd(bench='.'):
++	f = get_env_cmd('python', bench=bench)
++	os.chdir(os.path.join(bench, 'sites'))
++	os.execv(f, [f] + ['-m', 'frappe.utils.bench_helper'] + sys.argv[1:])
++
++def frappe_cmd(bench='.'):
++	f = get_env_cmd('python', bench=bench)
++	os.chdir(os.path.join(bench, 'sites'))
++	os.execv(f, [f] + ['-m', 'frappe.utils.bench_helper', 'frappe'] + sys.argv[1:])
++
++def get_frappe_commands(bench='.'):
++	python = get_env_cmd('python', bench=bench)
++	sites_path = os.path.join(bench, 'sites')
++	if not os.path.exists(sites_path):
++		return []
++	try:
++		return json.loads(get_cmd_output("{python} -m frappe.utils.bench_helper get-frappe-commands".format(python=python), cwd=sites_path))
++	except subprocess.CalledProcessError:
++		return []
++
++def get_frappe_help(bench='.'):
++	python = get_env_cmd('python', bench=bench)
++	sites_path = os.path.join(bench, 'sites')
++	if not os.path.exists(sites_path):
++		return []
++	try:
++		out = get_cmd_output("{python} -m frappe.utils.bench_helper get-frappe-help".format(python=python), cwd=sites_path)
++		return "Framework commands:\n" + out.split('Commands:')[1]
++	except subprocess.CalledProcessError:
++		return ""
++
+ @click.command()
+ def shell(bench='.'):
+ 	if not os.environ.get('SHELL'):
+@@ -86,6 +142,8 @@ def shell(bench='.'):
+ def bench(bench='.'):
+ 	"Bench manager for Frappe"
+ 	# TODO add bench path context
++	global FRAPPE_VERSION
++	FRAPPE_VERSION = get_current_frappe_version()
+ 	setup_logging(bench=bench)
+ 
+ @click.command()
+@@ -134,8 +192,9 @@ def new_site(site, mariadb_root_password
+ @click.option('--requirements',flag_value=True, type=bool, help="Update requirements")
+ @click.option('--restart-supervisor',flag_value=True, type=bool, help="restart supervisor processes after update")
+ @click.option('--auto',flag_value=True, type=bool)
++@click.option('--upgrade',flag_value=True, type=bool)
+ @click.option('--no-backup',flag_value=True, type=bool)
+-def update(pull=False, patch=False, build=False, bench=False, auto=False, restart_supervisor=False, requirements=False, no_backup=False):
++def update(pull=False, patch=False, build=False, bench=False, auto=False, restart_supervisor=False, requirements=False, no_backup=False, upgrade=False):
+ 	"Update bench"
+ 
+ 	if not (pull or patch or build or bench or requirements):
+@@ -155,12 +214,36 @@ def update(pull=False, patch=False, buil
+ 				'build': build,
+ 				'requirements': requirements,
+ 				'no-backup': no_backup,
+-				'restart-supervisor': restart_supervisor
++				'restart-supervisor': restart_supervisor,
++				'upgrade': upgrade
+ 		})
++
++	version_upgrade = is_version_upgrade()
++
++	if version_upgrade and not upgrade:
++		print
++		print
++		print "This update will cause a major version change in Frappe/ERPNext from {0} to {1} (beta).".format(*version_upgrade)
++		print "This would take significant time to migrate and might break custom apps. Please run `bench update --upgrade` to confirm."
++		print 
++		# print "You can also pin your bench to {0} by running `bench swtich-to-v{0}`".format(version_upgrade[0])
++		print "You can stay on the latest stable release by running `bench switch-to-master` or pin your bench to {0} by running `bench swtich-to-v{0}`".format(version_upgrade[0])
++		sys.exit(1)
++	elif not version_upgrade and upgrade:
++		upgrade = False
++
+ 	if pull:
+ 		pull_all_apps()
++
+ 	if requirements:
+ 		update_requirements()
++
++	if upgrade:
++		pre_upgrade(version_upgrade[0], version_upgrade[1])
++		import utils, app
++		reload(utils)
++		reload(app)
++
+ 	if patch:
+ 		if not no_backup:
+ 			backup_all_sites()
+@@ -169,14 +252,23 @@ def update(pull=False, patch=False, buil
+ 		build_assets()
+ 	if restart_supervisor or conf.get('restart_supervisor_on_update'):
+ 		restart_supervisor_processes()
++	if upgrade:
++		post_upgrade(version_upgrade[0], version_upgrade[1])
+ 
+ 	print "_"*80
+ 	print "https://frappe.io/buy - Donate to help make better free and open source tools"
+ 	print
+ 
++@click.command('retry-upgrade')
++@click.option('--version', default=5)
++def retry_upgrade(version):
++	pull_all_apps()
++	patch_sites()
++	build_assets()
++	post_upgrade(version-1, version)
++
+ def restart_update(kwargs):
+ 	args = ['--'+k for k, v in kwargs.items() if v]
+-	print 'restarting '
+ 	os.execv(sys.argv[0], sys.argv[:2] + args)
+ 
+ @click.command('restart')
+@@ -198,6 +290,33 @@ def migrate_3to4(path):
+ 			migrate_3to4=os.path.join(os.path.dirname(__file__), 'migrate3to4.py'),
+ 			site=path))
+ 
++@click.command('switch-to-master')
++@click.option('--upgrade',flag_value=True, type=bool)
++def _switch_to_master(upgrade=False):
++	"Switch frappe and erpnext to master branch"
++	switch_to_master(upgrade=upgrade)
++	print 
++	print 'Switched to master'
++	print 'Please run `bench update --patch` to be safe from any differences in database schema'
++
++@click.command('switch-to-develop')
++@click.option('--upgrade',flag_value=True, type=bool)
++def _switch_to_develop(upgrade=False):
++	"Switch frappe and erpnext to develop branch"
++	switch_to_develop(upgrade=upgrade)
++	print 
++	print 'Switched to develop'
++	print 'Please run `bench update --patch` to be safe from any differences in database schema'
++		
++@click.command('switch-to-v4')
++@click.option('--upgrade',flag_value=True, type=bool)
++def _switch_to_v4(upgrade=False):
++	"Switch frappe and erpnext to v4 branch"
++	switch_to_v4(upgrade=upgrade)
++	print 
++	print 'Switched to v4'
++	print 'Please run `bench update --patch` to be safe from any differences in database schema'
++
+ @click.command('set-nginx-port')
+ @click.argument('site')
+ @click.argument('port', type=int)
+@@ -205,6 +324,27 @@ def set_nginx_port(site, port):
+ 	"Set nginx port for site"
+ 	_set_nginx_port(site, port)
+ 
++@click.command('set-ssl-certificate')
++@click.argument('site')
++@click.argument('ssl-certificate-path')
++def _set_ssl_certificate(site, ssl_certificate_path):
++	"Set ssl certificate path for site"
++	set_ssl_certificate(site, ssl_certificate_path)
++
++@click.command('set-ssl-key')
++@click.argument('site')
++@click.argument('ssl-certificate-key-path')
++def _set_ssl_certificate_key(site, ssl_certificate_key_path):
++	"Set ssl certificate private key path for site"
++	set_ssl_certificate_key(site, ssl_certificate_key_path)
++
++@click.command('set-url-root')
++@click.argument('site')
++@click.argument('url-root')
++def set_url_root(site, url_root):
++	"Set url root for site"
++	_set_url_root(site, url_root)
++
+ @click.command('set-mariadb-host')
+ @click.argument('host')
+ def _set_mariadb_host(host):
+@@ -239,11 +379,13 @@ def _prime_wheel_cache():
+ @click.command('release')
+ @click.argument('app', type=click.Choice(['frappe', 'erpnext', 'shopping_cart']))
+ @click.argument('bump-type', type=click.Choice(['major', 'minor', 'patch']))
+-def _release(app, bump_type):
++@click.option('--develop', default='develop')
++@click.option('--master', default='master')
++def _release(app, bump_type, develop, master):
+ 	"Release app (internal to the Frappe team)"
+ 	from .release import release
+ 	repo = os.path.join('apps', app)
+-	release(repo, bump_type)
++	release(repo, bump_type, develop, master)
+ 
+ ## Setup
+ @click.group()
+@@ -267,6 +409,11 @@ def setup_supervisor():
+ 	"generate config for supervisor"
+ 	generate_supervisor_config()
+ 	
++@click.command('redis-cache')
++def setup_redis_cache():
++	"generate config for redis cache"
++	generate_redis_config()
++	
+ @click.command('production')
+ @click.argument('user')
+ def setup_production(user):
+@@ -305,6 +452,7 @@ def setup_config():
+ setup.add_command(setup_nginx)
+ setup.add_command(setup_sudoers)
+ setup.add_command(setup_supervisor)
++setup.add_command(setup_redis_cache)
+ setup.add_command(setup_auto_update)
+ setup.add_command(setup_dnsmasq)
+ setup.add_command(setup_backups)
+@@ -380,40 +528,32 @@ config.add_command(config_http_timeout)
+ def patch():
+ 	pass
+ 
+-@click.command('fix-perms')
+-def _fix_perms():
++@click.command('fix-prod-perms')
++def _fix_prod_perms():
++	"Fix permissions if supervisor processes were run as root"
+ 	if os.path.exists("config/supervisor.conf"):
+ 		exec_cmd("supervisorctl stop frappe:")
+ 
+-	"Fix permissions if supervisor processes were run as root"
+-	files = [
+-	"logs/web.error.log",
+-	"logs/web.log",
+-	"logs/workerbeat.error.log",
+-	"logs/workerbeat.log",
+-	"logs/worker.error.log",
+-	"logs/worker.log",
+-	"config/nginx.conf",
+-	"config/supervisor.conf",
+-	]
+-
+-	frappe_user = get_config().get('frappe_user')
+-	if not frappe_user:
+-		print "frappe user not set"
+-		sys.exit(1)
+-
+-	for path in files:
+-		if os.path.exists(path):
+-			uid = pwd.getpwnam(frappe_user).pw_uid
+-			gid = grp.getgrnam(frappe_user).gr_gid
+-			os.chown(path, uid, gid)
++	fix_prod_setup_perms()
+ 
+ 	if os.path.exists("config/supervisor.conf"):
+ 		exec_cmd("{bench} setup supervisor".format(bench=sys.argv[0]))
+ 		exec_cmd("supervisorctl reload")
+ 
+ 
+-patch.add_command(_fix_perms)
++@click.command('fix-file-perms')
++def _fix_file_perms():
++	"Fix file permissions"
++	fix_file_perms()
++
++patch.add_command(_fix_file_perms)
++patch.add_command(_fix_prod_perms)
++
++
++@click.command('download-translations')
++def _download_translations():
++	"Download latest translations"
++	download_translations_p()
+ 
+ #Bench commands
+ 
+@@ -427,12 +567,20 @@ bench.add_command(restart)
+ bench.add_command(config)
+ bench.add_command(start)
+ bench.add_command(set_nginx_port)
++bench.add_command(_set_ssl_certificate)
++bench.add_command(_set_ssl_certificate_key)
+ bench.add_command(_set_mariadb_host)
+ bench.add_command(set_default_site)
+ bench.add_command(migrate_3to4)
++bench.add_command(_switch_to_master)
++bench.add_command(_switch_to_develop)
++bench.add_command(_switch_to_v4)
+ bench.add_command(shell)
+ bench.add_command(_backup_all_sites)
+ bench.add_command(_backup_site)
+ bench.add_command(_prime_wheel_cache)
+ bench.add_command(_release)
+ bench.add_command(patch)
++bench.add_command(set_url_root)
++bench.add_command(retry_upgrade)
++bench.add_command(_download_translations)
+--- bench/config.py.orig	2014-11-19 06:36:44 UTC
++++ bench/config.py
+@@ -1,12 +1,27 @@
+ import os
+ import getpass
+ import json
++import subprocess
++import shutil
+ from jinja2 import Environment, PackageLoader
+-from .utils import get_sites, get_config, update_config
++from .utils import get_sites, get_config, update_config, get_redis_version
+ 
+ env = Environment(loader=PackageLoader('bench', 'templates'), trim_blocks=True)
+ 
++def write_config_file(bench, file_name, config):
++	config_path = os.path.join(bench, 'config')
++	file_path = os.path.join(config_path, file_name)
++	number = (len([path for path in os.listdir(config_path) if path.startswith(file_name)]) -1 ) or ''
++	if number:
++		number = '.' + str(number)
++	if os.path.exists(file_path):
++		shutil.move(file_path, file_path + '.save' + number)
++
++	with open(file_path, 'wb') as f:
++		f.write(config)
++
+ def generate_supervisor_config(bench='.', user=None):
++	from .app import get_current_frappe_version
+ 	template = env.get_template('supervisor.conf')
+ 	bench_dir = os.path.abspath(bench)
+ 	sites_dir = os.path.join(bench_dir, "sites")
+@@ -20,9 +35,11 @@ def generate_supervisor_config(bench='.'
+ 		"sites_dir": sites_dir,
+ 		"user": user,
+ 		"http_timeout": config.get("http_timeout", 120),
++		"redis_server": subprocess.check_output('which redis-server', shell=True).strip(),
++		"redis_config": os.path.join(bench_dir, 'config', 'redis.conf'),
++		"frappe_version": get_current_frappe_version()
+ 	})
+-	with open("config/supervisor.conf", 'w') as f:
+-		f.write(config)
++	write_config_file(bench, 'supervisor.conf', config)
+ 	update_config({'restart_supervisor_on_update': True})
+ 
+ def get_site_config(site, bench='.'):
+@@ -31,10 +48,16 @@ def get_site_config(site, bench='.'):
+ 
+ def get_sites_with_config(bench='.'):
+ 	sites = get_sites()
+-	return [{
+-		"name": site, 
+-		"port": get_site_config(site, bench=bench).get('nginx_port')
+-	} for site in sites]
++	ret = []
++	for site in sites:
++		site_config = get_site_config(site, bench=bench)
++		ret.append({
++			"name": site,
++			"port": site_config.get('nginx_port'),
++			"ssl_certificate": site_config.get('ssl_certificate'),
++			"ssl_certificate_key": site_config.get('ssl_certificate_key')
++		})
++	return ret
+ 
+ def generate_nginx_config(bench='.'):
+ 	template = env.get_template('nginx.conf')
+@@ -59,5 +82,14 @@ def generate_nginx_config(bench='.'):
+ 		"dns_multitenant": get_config().get('dns_multitenant'),
+ 		"sites": sites
+ 	})
+-	with open("config/nginx.conf", 'w') as f:
+-		f.write(config)
++	write_config_file(bench, 'nginx.conf', config)
++
++def generate_redis_config(bench='.'):
++	template = env.get_template('redis.conf')
++	conf = {
++		"maxmemory": get_config().get('cache_maxmemory', '50'),
++		"port": get_config().get('redis_cache_port', '11311'),
++		"redis_version": get_redis_version()
++	}
++	config = template.render(**conf)
++	write_config_file(bench, 'redis.conf', config)
+--- bench/migrate_to_v5.py.orig	2015-07-31 10:19:27 UTC
++++ bench/migrate_to_v5.py
+@@ -0,0 +1,46 @@
++from .utils import exec_cmd, get_frappe, run_frappe_cmd
++from .release import get_current_version
++from .app import remove_from_appstxt
++import os
++import shutil
++import sys
++
++repos = ('frappe', 'erpnext')
++
++def migrate_to_v5(bench='.'):
++	validate_v4(bench=bench)
++	for repo in repos:
++		checkout_v5(repo, bench=bench)
++	remove_shopping_cart(bench=bench)
++	exec_cmd("{bench} update".format(bench=sys.argv[0]))
++
++def remove_shopping_cart(bench='.'):
++	archived_apps_dir = os.path.join(bench, 'archived_apps')
++	shopping_cart_dir = os.path.join(bench, 'apps', 'shopping_cart')
++
++	if not os.path.exists(shopping_cart_dir):
++		return
++
++	run_frappe_cmd('--site', 'all', 'remove-from-installed-apps', 'shopping_cart', bench=bench)
++	remove_from_appstxt('shopping_cart', bench=bench)
++	exec_cmd("{pip} --no-input uninstall -y shopping_cart".format(pip=os.path.join(bench, 'env', 'bin', 'pip')))
++
++	if not os.path.exists(archived_apps_dir):
++		os.mkdir(archived_apps_dir)
++	shutil.move(shopping_cart_dir, archived_apps_dir)
++
++def validate_v4(bench='.'):
++	for repo in repos:
++		path = os.path.join(bench, 'apps', repo)
++		if os.path.exists(path):
++			current_version = get_current_version(path)
++			if not current_version.startswith('4'):
++				raise Exception("{} is not on v4.x.x".format(repo))
++
++def checkout_v5(repo, bench='.'):
++	cwd = os.path.join(bench, 'apps', repo)
++	if os.path.exists(cwd):
++		exec_cmd("git fetch upstream", cwd=cwd)
++		exec_cmd("git checkout v5.0", cwd=cwd)
++		exec_cmd("git clean -df", cwd=cwd)
++
+--- bench/production_setup.py.orig	2014-11-19 06:36:44 UTC
++++ bench/production_setup.py
+@@ -1,17 +1,16 @@
+-from .utils import get_program, exec_cmd, get_cmd_output
++from .utils import get_program, exec_cmd, get_cmd_output, fix_prod_setup_perms
+ from .config import generate_nginx_config, generate_supervisor_config
+ from jinja2 import Environment, PackageLoader
+ import os
+ import shutil
+ 
+ def restart_service(service):
+-	program = get_program(['systemctl', 'service'])
+-	if not program:
++	if os.path.basename(get_program(['systemctl']) or '') == 'systemctl' and is_running_systemd():
++		exec_cmd("{prog} restart {service}".format(prog='systemctl', service=service))
++	elif os.path.basename(get_program(['service']) or '') == 'service':
++		exec_cmd("{prog} {service} restart ".format(prog='service', service=service))
++	else:
+ 		raise Exception, 'No service manager found'
+-	elif os.path.basename(program) == 'systemctl':
+-		exec_cmd("{prog} restart {service}".format(prog=program, service=service))
+-	elif os.path.basename(program) == 'service':
+-		exec_cmd("{prog} {service} restart ".format(prog=program, service=service))
+ 
+ def get_supervisor_confdir():
+ 	possiblities = ('/etc/supervisor/conf.d', '/etc/supervisor.d/', '/etc/supervisord/conf.d', '/etc/supervisord.d')
+@@ -30,6 +29,14 @@ def remove_default_nginx_configs():
+ 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'
+ 			
++def is_running_systemd():
++	with open('/proc/1/comm') as f:
++		comm = f.read().strip()
++	if comm == "init":
++		return False
++	elif comm == "systemd":
++		return True
++	return False
+ 
+ def copy_default_nginx_config():
+ 	shutil.copy(os.path.join(os.path.dirname(__file__), 'templates', 'nginx_default.conf'), '/etc/nginx/nginx.conf')
+@@ -37,6 +44,7 @@ def copy_default_nginx_config():
+ def setup_production(user, bench='.'):
+ 	generate_supervisor_config(bench=bench, user=user)
+ 	generate_nginx_config(bench=bench)
++	fix_prod_setup_perms(frappe_user=user)
+ 	remove_default_nginx_configs()
+ 
+ 	if is_centos7():
+--- bench/release.py.orig	2014-11-19 06:36:44 UTC
++++ bench/release.py
+@@ -34,10 +34,10 @@ def create_release(repo_path, version, r
+ 	g.merge(master_branch)
+ 	return tag_name
+ 
+-def push_release(repo_path):
++def push_release(repo_path, develop_branch='develop', master_branch='master'):
+ 	repo = git.Repo(repo_path)
+ 	g = repo.git
+-	print g.push('upstream', 'master:master', 'develop:develop', '--tags')
++	print g.push('upstream', '{master}:{master}'.format(master=master_branch), '{develop}:{develop}'.format(develop=develop_branch), '--tags')
+ 
+ def create_github_release(owner, repo, tag_name, log, gh_username=None, gh_password=None):
+ 	global github_username, github_password
+@@ -137,25 +137,40 @@ def get_current_version(repo):
+ 				contents)
+ 		return match.group(2)
+ 
+-def bump_repo(repo, bump_type):
+-		update_branch(repo, 'master', remote='upstream')
+-		update_branch(repo, 'develop', remote='upstream')
+-		git.Repo(repo).git.checkout('develop')
+-		current_version = get_current_version(repo)
+-		new_version = get_bumped_version(current_version, bump_type)
+-		set_version(repo, new_version)
+-		return new_version
++def check_for_unmerged_changelog(repo):
++	current = os.path.join(repo, os.path.basename(repo), 'change_log', 'current')
++	if os.path.exists(current) and [f for f in os.listdir(current) if f != "readme.md"]:
++		raise Exception("Unmerged change log! in " + repo)
+ 
+-def bump(repo, bump_type):
++def bump_repo(repo, bump_type, develop='develop', master='master', remote='upstream'):
++	update_branch(repo, master, remote=remote)
++	update_branch(repo, develop, remote=remote)
++	git.Repo(repo).git.checkout(develop)
++	check_for_unmerged_changelog(repo)
++	current_version = get_current_version(repo)
++	new_version = get_bumped_version(current_version, bump_type)
++	set_version(repo, new_version)
++	return new_version
++
++def get_release_message(repo_path, develop_branch='develop', master_branch='master'):
++	repo = git.Repo(repo_path)
++	g = repo.git
++	return "* " + g.log('upstream/{master_branch}..upstream/{develop_branch}'.format(master_branch=master_branch, develop_branch=develop_branch), '--format=format:%s', '--no-merges').replace('\n', '\n* ')
++
++def bump(repo, bump_type, develop='develop', master='master', remote='upstream'):
+ 	assert bump_type in ['minor', 'major', 'patch']
+-	new_version = bump_repo(repo, bump_type)
++	new_version = bump_repo(repo, bump_type, develop=develop, master=master, remote=remote)
++	message = get_release_message(repo, develop_branch=develop, master_branch=master)
++	print
++	print message
++	print
+ 	commit_changes(repo, new_version)
+-	tag_name = create_release(repo, new_version)
+-	push_release(repo)
+-	create_github_release('frappe', repo, tag_name, '')
++	tag_name = create_release(repo, new_version, develop_branch=develop, master_branch=master)
++	push_release(repo, develop_branch=develop, master_branch=master)
++	create_github_release('frappe', repo, tag_name, message)
+ 	print 'Released {tag} for {repo}'.format(tag=tag_name, repo=repo)
+ 
+-def release(repo, bump_type):
++def release(repo, bump_type, develop, master):
+ 	if not get_config().get('release_bench'):
+ 		print 'bench not configured to release'
+ 		sys.exit(1)
+@@ -164,7 +179,7 @@ def release(repo, bump_type):
+ 	github_password = getpass.getpass()
+ 	r = requests.get('https://api.github.com/user', auth=HTTPBasicAuth(github_username, github_password))
+ 	r.raise_for_status()
+-	bump(repo, bump_type)
++	bump(repo, bump_type, develop=develop, master=master)
+ 
+ if __name__ == "__main__":
+ 	main()
+--- bench/templates/nginx.conf.orig	2014-11-19 06:36:44 UTC
++++ bench/templates/nginx.conf
+@@ -5,15 +5,7 @@ upstream frappe {
+     server 127.0.0.1:8000 fail_timeout=0;
+ }
+ 
+-{% macro server_block(site, port=80, default=False, server_name=None, sites=None, dns_multitenant=False) -%}
+-	server {
+-		listen {{ site.port if not default and site.port else port }} {% if default %} default {% endif %};
+-		client_max_body_size 4G;
+-		{% if dns_multitenant and sites %}
+-			server_name {% for site in sites %} {{ site.name }} {% endfor %};
+-		{% else %}
+-			server_name {{ site.name if not server_name else server_name }};
+-		{% endif %}
++{% macro location_block(site, port=80, default=False, server_name=None, sites=None, dns_multitenant=False) -%}
+ 		keepalive_timeout 5;
+ 		sendfile on;
+ 		root {{ sites_dir }};
+@@ -34,30 +26,66 @@ upstream frappe {
+ 		location @magic {
+ 			proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ 			{% if not dns_multitenant %}
+-			proxy_set_header Host {{ site.name }};
+-			{% else %}
+-			proxy_set_header Host $host;
++			proxy_set_header X-Frappe-Site-Name {{ site.name }};
+ 			{% endif %}
++			proxy_set_header Host $host;
+ 			proxy_set_header X-Use-X-Accel-Redirect True;
+ 			proxy_read_timeout {{http_timeout}};
+ 			proxy_redirect off;
+ 			proxy_pass  http://frappe;
+ 		}
++{%- endmacro %}
++
++{% macro server_name_block(site, default=False, server_name=None, sites=None, dns_multitenant=False) -%}
++		client_max_body_size 4G;
++		{% if dns_multitenant and sites %}
++			server_name {% for site in sites %} {{ site.name }} {% endfor %};
++		{% else %}
++			server_name {{ site.name if not server_name else server_name }};
++		{% endif %}
++{%- endmacro %}
++
++{% macro server_block_http(site, port=80, default=False, server_name=None, sites=None, dns_multitenant=False) -%}
++	server {
++		listen {{ site.port if not default and site.port else port }} {% if default %} default {% endif %};
++		{{ server_name_block(site, default=default, server_name=server_name, sites=sites, dns_multitenant=dns_multitenant) }}
++		{{ location_block(site, port=port, default=default, server_name=server_name, sites=sites, dns_multitenant=dns_multitenant) }}
++	}
++{%- endmacro %}
++
++{% macro server_block_https(site, port=443, default=False, server_name=None, sites=None, dns_multitenant=False) -%}
++	server {
++		listen {{ site.ssl_port if not default and site.ssl_port else port }} {% if default %} default {% endif %};
++		{{ server_name_block(site, default=default, server_name=server_name, sites=sites, dns_multitenant=dns_multitenant) }}
++
++		ssl on;
++		ssl_certificate      {{ site.ssl_certificate }};
++		ssl_certificate_key  {{ site.ssl_certificate_key }};
++		ssl_session_timeout  5m;
++		ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
++		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";
++		ssl_prefer_server_ciphers   on;

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201507311405.t6VE5xVM047225>