Advertisement
Guest User

Untitled

a guest
Jan 12th, 2020
316
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 14.89 KB | None | 0 0
  1. #!/usr/bin/env python3
  2. import os, sys, subprocess, getpass, json, multiprocessing, shutil, platform, warnings, datetime
  3.  
  4. tmp_bench_repo = os.path.join('/', 'tmp', '.bench')
  5. tmp_log_folder = os.path.join('/', 'tmp', 'logs')
  6. execution_timestamp = datetime.datetime.utcnow()
  7. execution_day = "{:%Y-%m-%d}".format(execution_timestamp)
  8. execution_time = "{:%H:%M}".format(execution_timestamp)
  9. log_file_name = "easy-install__{0}__{1}.log".format(execution_day, execution_time.replace(':', '-'))
  10. log_path = os.path.join(tmp_log_folder, log_file_name)
  11. log_stream = sys.stdout
  12.  
  13.  
  14. def log(message, level=0):
  15. levels = {
  16. 0: '\033[94m', # normal
  17. 1: '\033[92m', # success
  18. 2: '\033[91m', # fail
  19. 3: '\033[93m' # warn/suggest
  20. }
  21. start = levels.get(level) or ''
  22. end = '\033[0m'
  23. print(start + message + end)
  24.  
  25.  
  26. def setup_log_stream(args):
  27. global log_stream
  28. sys.stderr = sys.stdout
  29.  
  30. if not args.verbose:
  31. if not os.path.exists(tmp_log_folder):
  32. os.makedirs(tmp_log_folder)
  33. log_stream = open(log_path, 'w')
  34. log("Logs are saved under {0}".format(log_path), level=3)
  35. print("Install script run at {0} on {1}\n\n".format(execution_time, execution_day), file=log_stream)
  36.  
  37.  
  38. def check_environment():
  39. needed_environ_vars = ['LANG', 'LC_ALL']
  40. message = ''
  41.  
  42. for var in needed_environ_vars:
  43. if var not in os.environ:
  44. message += "\nexport {0}=C.UTF-8".format(var)
  45.  
  46. if message:
  47. log("Bench's CLI needs these to be defined!", level=3)
  48. log("Run the following commands in shell: {0}".format(message), level=2)
  49. sys.exit()
  50.  
  51.  
  52. def check_system_package_managers():
  53. if 'Darwin' in os.uname():
  54. if not shutil.which('brew'):
  55. raise Exception('''
  56. Please install brew package manager before proceeding with bench setup. Please run following
  57. to install brew package manager on your machine,
  58.  
  59. /usr/bin/ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
  60. ''')
  61. if 'Linux' in os.uname():
  62. if not any([shutil.which(x) for x in ['apt-get', 'yum']]):
  63. raise Exception('Cannot find any compatible package manager!')
  64.  
  65.  
  66. def check_distribution_compatibility():
  67. dist_name, dist_version = get_distribution_info()
  68. supported_dists = {
  69. 'macos': [10.9, 10.10, 10.11, 10.12],
  70. 'ubuntu': [14, 15, 16, 18, 19],
  71. 'debian': [8, 9],
  72. 'centos': [7]
  73. }
  74.  
  75. log("Checking System Compatibility...")
  76. if dist_name in supported_dists:
  77. if float(dist_version) in supported_dists[dist_name]:
  78. log("{0} {1} is compatible!".format(dist_name, dist_version), level=1)
  79. else:
  80. log("{0} {1} is detected".format(dist_name, dist_version), level=1)
  81. log("Install on {0} {1} instead".format(dist_name, supported_dists[dist_name][-1]), level=3)
  82. else:
  83. log("Sorry, the installer doesn't support {0}. Aborting installation!".format(dist_name), level=2)
  84.  
  85.  
  86. def get_distribution_info():
  87. # return distribution name and major version
  88. if platform.system() == "Linux":
  89. current_dist = platform.dist()
  90. return current_dist[0].lower(), current_dist[1].rsplit('.')[0]
  91.  
  92. elif platform.system() == "Darwin":
  93. current_dist = platform.mac_ver()
  94. return "macos", current_dist[0].rsplit('.', 1)[0]
  95.  
  96.  
  97. def run_os_command(command_map):
  98. '''command_map is a dictionary of {'executable': command}. For ex. {'apt-get': 'sudo apt-get install -y python2.7'}'''
  99. success = True
  100.  
  101. for executable, commands in command_map.items():
  102. if shutil.which(executable):
  103. if isinstance(commands, str):
  104. commands = [commands]
  105.  
  106. for command in commands:
  107. returncode = subprocess.check_call(command, shell=True, stdout=log_stream, stderr=sys.stderr)
  108. success = success and (returncode == 0)
  109.  
  110. return success
  111.  
  112.  
  113. def install_prerequisites():
  114. # pre-requisites for bench repo cloning
  115. run_os_command({
  116. 'apt-get': [
  117. 'sudo apt-get update',
  118. 'sudo apt-get install -y git build-essential python3-setuptools python3-dev libffi-dev'
  119. ],
  120. 'yum': [
  121. 'sudo yum groupinstall -y "Development tools"',
  122. 'sudo yum install -y epel-release redhat-lsb-core git python-setuptools python-devel openssl-devel libffi-devel'
  123. ]
  124. })
  125.  
  126. install_package('curl')
  127. install_package('wget')
  128. install_package('git')
  129. install_package('pip3', 'python3-pip')
  130.  
  131. success = run_os_command({
  132. 'python3': "sudo -H python3 -m pip install --upgrade setuptools cryptography ansible==2.8.5 pip"
  133. })
  134.  
  135. if not (success or shutil.which('ansible')):
  136. could_not_install('Ansible')
  137.  
  138.  
  139. def could_not_install(package):
  140. raise Exception('Could not install {0}. Please install it manually.'.format(package))
  141.  
  142.  
  143. def is_sudo_user():
  144. return os.geteuid() == 0
  145.  
  146.  
  147. def install_package(package, package_name=None):
  148. if shutil.which(package):
  149. log("{0} already installed!".format(package), level=1)
  150. else:
  151. log("Installing {0}...".format(package))
  152. package_name = package_name or package
  153. success = run_os_command({
  154. 'apt-get': ['sudo apt-get install -y {0}'.format(package_name)],
  155. 'yum': ['sudo yum install -y {0}'.format(package_name)],
  156. 'brew': ['brew install {0}'.format(package_name)]
  157. })
  158. if success:
  159. log("{0} installed!".format(package), level=1)
  160. return success
  161. could_not_install(package)
  162.  
  163.  
  164. def install_bench(args):
  165. # clone bench repo
  166. if not args.run_travis:
  167. clone_bench_repo(args)
  168.  
  169. if not args.user:
  170. if args.production:
  171. args.user = 'frappe'
  172.  
  173. elif 'SUDO_USER' in os.environ:
  174. args.user = os.environ['SUDO_USER']
  175.  
  176. else:
  177. args.user = getpass.getuser()
  178.  
  179. if args.user == 'root':
  180. raise Exception('Please run this script as a non-root user with sudo privileges, but without using sudo or pass --user=USER')
  181.  
  182. # Python executable
  183. dist_name, dist_version = get_distribution_info()
  184. if dist_name=='centos':
  185. args.python = 'python3.6'
  186. else:
  187. args.python = 'python3'
  188.  
  189. # create user if not exists
  190. extra_vars = vars(args)
  191. extra_vars.update(frappe_user=args.user)
  192.  
  193. if os.path.exists(tmp_bench_repo):
  194. repo_path = tmp_bench_repo
  195.  
  196. else:
  197. repo_path = os.path.join(os.path.expanduser('~'), 'bench')
  198.  
  199. extra_vars.update(repo_path=repo_path)
  200. run_playbook('create_user.yml', extra_vars=extra_vars)
  201.  
  202. extra_vars.update(get_passwords(args))
  203. if args.production:
  204. extra_vars.update(max_worker_connections=multiprocessing.cpu_count() * 1024)
  205.  
  206. frappe_branch = 'version-12'
  207. erpnext_branch = 'version-12'
  208.  
  209. if args.version:
  210. if args.version <= 10:
  211. frappe_branch = "{0}.x.x".format(args.version)
  212. erpnext_branch = "{0}.x.x".format(args.version)
  213. else:
  214. frappe_branch = "version-{0}".format(args.version)
  215. erpnext_branch = "version-{0}".format(args.version)
  216. else:
  217. if args.frappe_branch:
  218. frappe_branch = args.frappe_branch
  219.  
  220. if args.erpnext_branch:
  221. erpnext_branch = args.erpnext_branch
  222.  
  223. extra_vars.update(frappe_branch=frappe_branch)
  224. extra_vars.update(erpnext_branch=erpnext_branch)
  225.  
  226. bench_name = 'frappe-bench' if not args.bench_name else args.bench_name
  227. extra_vars.update(bench_name=bench_name)
  228.  
  229. # Will install ERPNext production setup by default
  230. run_playbook('site.yml', sudo=True, extra_vars=extra_vars)
  231.  
  232. if os.path.exists(tmp_bench_repo):
  233. shutil.rmtree(tmp_bench_repo)
  234.  
  235.  
  236. def clone_bench_repo(args):
  237. '''Clones the bench repository in the user folder'''
  238. branch = args.bench_branch or 'master'
  239. repo_url = args.repo_url or 'https://github.com/frappe/bench'
  240.  
  241. if os.path.exists(tmp_bench_repo):
  242. return 0
  243. elif args.without_bench_setup:
  244. clone_path = os.path.join(os.path.expanduser('~'), 'bench')
  245. else:
  246. clone_path = tmp_bench_repo
  247.  
  248. success = run_os_command(
  249. {'git': 'git clone --quiet {repo_url} {bench_repo} --depth 1 --branch {branch}'.format(
  250. repo_url=repo_url, bench_repo=clone_path, branch=branch)}
  251. )
  252.  
  253. return success
  254.  
  255.  
  256. def passwords_didnt_match(context=''):
  257. log("{} passwords did not match!".format(context), level=3)
  258.  
  259.  
  260. def get_passwords(args):
  261. """
  262. Returns a dict of passwords for further use
  263. and creates passwords.txt in the bench user's home directory
  264. """
  265. log("Input MySQL and Frappe Administrator passwords:")
  266. ignore_prompt = args.run_travis or args.without_bench_setup
  267. mysql_root_password, admin_password = '', ''
  268. passwords_file_path = os.path.join(os.path.expanduser('~' + args.user), 'passwords.txt')
  269.  
  270. if not ignore_prompt:
  271. # set passwords from existing passwords.txt
  272. if os.path.isfile(passwords_file_path):
  273. with open(passwords_file_path, 'r') as f:
  274. passwords = json.load(f)
  275. mysql_root_password, admin_password = passwords['mysql_root_password'], passwords['admin_password']
  276.  
  277. # set passwords from cli args
  278. if args.mysql_root_password:
  279. mysql_root_password = args.mysql_root_password
  280. if args.admin_password:
  281. admin_password = args.admin_password
  282.  
  283. # prompt for passwords
  284. pass_set = True
  285. while pass_set:
  286. # mysql root password
  287. if not mysql_root_password:
  288. mysql_root_password = getpass.unix_getpass(prompt='Please enter mysql root password: ')
  289. conf_mysql_passwd = getpass.unix_getpass(prompt='Re-enter mysql root password: ')
  290.  
  291. if mysql_root_password != conf_mysql_passwd or mysql_root_password == '':
  292. passwords_didnt_match("MySQL")
  293. mysql_root_password = ''
  294. continue
  295.  
  296. # admin password
  297. if not admin_password:
  298. admin_password = getpass.unix_getpass(prompt='Please enter the default Administrator user password: ')
  299. conf_admin_passswd = getpass.unix_getpass(prompt='Re-enter Administrator password: ')
  300.  
  301. if admin_password != conf_admin_passswd or admin_password == '':
  302. passwords_didnt_match("Administrator")
  303. admin_password = ''
  304. continue
  305.  
  306. pass_set = False
  307. else:
  308. mysql_root_password = admin_password = 'travis'
  309.  
  310. passwords = {
  311. 'mysql_root_password': mysql_root_password,
  312. 'admin_password': admin_password
  313. }
  314.  
  315. if not ignore_prompt:
  316. with open(passwords_file_path, 'w') as f:
  317. json.dump(passwords, f, indent=1)
  318.  
  319. log('Passwords saved at ~/passwords.txt')
  320.  
  321. return passwords
  322.  
  323.  
  324. def get_extra_vars_json(extra_args):
  325. # We need to pass production as extra_vars to the playbook to execute conditionals in the
  326. # playbook. Extra variables can passed as json or key=value pair. Here, we will use JSON.
  327. json_path = os.path.join('/', 'tmp', 'extra_vars.json')
  328. extra_vars = dict(list(extra_args.items()))
  329.  
  330. with open(json_path, mode='w') as j:
  331. json.dump(extra_vars, j, indent=1, sort_keys=True)
  332.  
  333. return ('@' + json_path)
  334.  
  335.  
  336. def run_playbook(playbook_name, sudo=False, extra_vars=None):
  337. args = ['ansible-playbook', '-c', 'local', playbook_name , '-vvvv']
  338.  
  339. if extra_vars:
  340. args.extend(['-e', get_extra_vars_json(extra_vars)])
  341.  
  342. if sudo:
  343. user = extra_vars.get('user') or getpass.getuser()
  344. args.extend(['--become', '--become-user={0}'.format(user)])
  345.  
  346. if os.path.exists(tmp_bench_repo):
  347. cwd = tmp_bench_repo
  348. else:
  349. cwd = os.path.join(os.path.expanduser('~'), 'bench')
  350.  
  351. success = subprocess.check_call(args, cwd=os.path.join(cwd, 'playbooks'), stdout=log_stream, stderr=sys.stderr)
  352. return success
  353.  
  354.  
  355. def parse_commandline_args():
  356. import argparse
  357.  
  358. parser = argparse.ArgumentParser(description='Frappe Installer')
  359. # Arguments develop and production are mutually exclusive both can't be specified together.
  360. # Hence, we need to create a group for discouraging use of both options at the same time.
  361. args_group = parser.add_mutually_exclusive_group()
  362.  
  363. args_group.add_argument('--develop', dest='develop', action='store_true', default=False, help='Install developer setup')
  364. args_group.add_argument('--production', dest='production', action='store_true', default=False, help='Setup Production environment for bench')
  365. parser.add_argument('--site', dest='site', action='store', default='site1.local', help='Specifiy name for your first ERPNext site')
  366. parser.add_argument('--without-site', dest='without_site', action='store_true', default=False)
  367. parser.add_argument('--verbose', dest='verbose', action='store_true', default=False, help='Run the script in verbose mode')
  368. parser.add_argument('--user', dest='user', help='Install frappe-bench for this user')
  369. parser.add_argument('--bench-branch', dest='bench_branch', help='Clone a particular branch of bench repository')
  370. parser.add_argument('--repo-url', dest='repo_url', help='Clone bench from the given url')
  371. parser.add_argument('--frappe-repo-url', dest='frappe_repo_url', action='store', default='https://github.com/frappe/frappe', help='Clone frappe from the given url')
  372. parser.add_argument('--frappe-branch', dest='frappe_branch', action='store', help='Clone a particular branch of frappe')
  373. parser.add_argument('--erpnext-repo-url', dest='erpnext_repo_url', action='store', default='https://github.com/frappe/erpnext', help='Clone erpnext from the given url')
  374. parser.add_argument('--erpnext-branch', dest='erpnext_branch', action='store', help='Clone a particular branch of erpnext')
  375. parser.add_argument('--without-erpnext', dest='without_erpnext', action='store_true', default=False, help='Prevent fetching ERPNext')
  376. # direct provision to install versions
  377. parser.add_argument('--version', dest='version', action='store', default='12', type=int, help='Clone particular version of frappe and erpnext')
  378. # To enable testing of script using Travis, this should skip the prompt
  379. parser.add_argument('--run-travis', dest='run_travis', action='store_true', default=False, help=argparse.SUPPRESS)
  380. parser.add_argument('--without-bench-setup', dest='without_bench_setup', action='store_true', default=False, help=argparse.SUPPRESS)
  381. # whether to overwrite an existing bench
  382. parser.add_argument('--overwrite', dest='overwrite', action='store_true', default=False, help='Whether to overwrite an existing bench')
  383. # set passwords
  384. parser.add_argument('--mysql-root-password', dest='mysql_root_password', help='Set mysql root password')
  385. parser.add_argument('--mariadb-version', dest='mariadb_version', default='10.2', help='Specify mariadb version')
  386. parser.add_argument('--admin-password', dest='admin_password', help='Set admin password')
  387. parser.add_argument('--bench-name', dest='bench_name', help='Create bench with specified name. Default name is frappe-bench')
  388. # Python interpreter to be used
  389. parser.add_argument('--python', dest='python', default='python3', help=argparse.SUPPRESS)
  390. # LXC Support
  391. parser.add_argument('--container', dest='container', default=False, action='store_true', help='Use if you\'re creating inside LXC')
  392. args = parser.parse_args()
  393.  
  394. return args
  395.  
  396. if __name__ == '__main__':
  397. if sys.version[0] == '2':
  398. if not raw_input("It is recommended to run this script with Python 3\nDo you still wish to continue? [Y/n]: ").lower() == "y":
  399. sys.exit()
  400.  
  401. if not is_sudo_user():
  402. log("Please run this script as a non-root user with sudo privileges", level=3)
  403. sys.exit()
  404.  
  405. args = parse_commandline_args()
  406.  
  407. with warnings.catch_warnings():
  408. warnings.simplefilter("ignore")
  409. setup_log_stream(args)
  410. check_distribution_compatibility()
  411. check_system_package_managers()
  412. check_environment()
  413. install_prerequisites()
  414. install_bench(args)
  415.  
  416. log("Bench + Frappe + ERPNext has been successfully installed!")
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement