blob: c57a9e2e7ff7b9d2c8941414d2b2ad99ee86bca8 [file] [log] [blame]
Rushabh Mehtae67d1fb2013-08-05 14:59:54 +05301# Copyright (c) 2013, Web Notes Technologies Pvt. Ltd.
2# License: GNU General Public License v3. See license.txt
3
Rushabh Mehta54c15452013-07-16 12:05:41 +05304#!/usr/bin/env python
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +05305from __future__ import unicode_literals
Rushabh Mehta54c15452013-07-16 12:05:41 +05306import os, sys
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +05307
Rushabh Mehta54c15452013-07-16 12:05:41 +05308apache_user = None
9is_redhat = is_debian = None
10root_password = None
11
12def install(install_path=None):
13 install_pre_requisites()
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +053014
Rushabh Mehta54c15452013-07-16 12:05:41 +053015 if not install_path:
16 install_path = os.getcwd()
17 install_erpnext(install_path)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +053018
Rushabh Mehta54c15452013-07-16 12:05:41 +053019 post_install(install_path)
20
21def install_pre_requisites():
22 global is_redhat, is_debian
23 is_redhat, is_debian = validate_install()
24 if is_redhat:
25 install_using_yum()
26 elif is_debian:
27 install_using_apt()
28
29 install_python_modules()
30
31 print "-"*80
32 print "Pre-requisites Installed"
33 print "-"*80
34
35def validate_install():
36 import platform
37
38 # check os
39 operating_system = platform.system()
40 print "Operating System =", operating_system
41 if operating_system != "Linux":
42 raise Exception, "Sorry! This installer works only for Linux based Operating Systems"
43
44 # check python version
45 python_version = sys.version.split(" ")[0]
46 print "Python Version =", python_version
47 if not (python_version and int(python_version.split(".")[0])==2 and int(python_version.split(".")[1]) >= 6):
48 raise Exception, "Hey! ERPNext needs Python version to be 2.6+"
49
50 # check distribution
51 distribution = platform.linux_distribution()[0].lower().replace('"', '')
52 print "Distribution = ", distribution
Anand Doshib1b564c2013-07-29 11:44:26 +053053 is_redhat = distribution in ("redhat", "centos", "centos linux", "fedora")
Nabin Haitada70992013-08-20 10:58:07 +053054 is_debian = distribution in ("debian", "ubuntu", "elementary os", "linuxmint")
Rushabh Mehta54c15452013-07-16 12:05:41 +053055
56 if not (is_redhat or is_debian):
57 raise Exception, "Sorry! This installer works only with yum or apt-get package management"
58
59 return is_redhat, is_debian
60
61def install_using_yum():
Anand Doshie7d77ca2013-08-14 15:12:46 +053062 packages = "python python-setuptools gcc python-devel MySQL-python httpd git memcached ntp vim-enhanced screen"
Rushabh Mehta54c15452013-07-16 12:05:41 +053063
64 print "-"*80
65 print "Installing Packages: (This may take some time)"
66 print packages
67 print "-"*80
68 exec_in_shell("yum install -y %s" % packages)
69
70 if not exec_in_shell("which mysql"):
71 packages = "mysql mysql-server mysql-devel"
72 print "Installing Packages:", packages
73 exec_in_shell("yum install -y %s" % packages)
74 exec_in_shell("service mysqld restart")
75
76 # set a root password post install
77 global root_password
78 print "Please create a password for root user of MySQL"
79 root_password = (get_root_password() or "erpnext").strip()
80 exec_in_shell('mysqladmin -u root password "%s"' % (root_password,))
81 print "Root password set as", root_password
82
83 # install htop
84 if not exec_in_shell("which htop"):
85 try:
86 exec_in_shell("cd /tmp && rpm -i --force http://packages.sw.be/rpmforge-release/rpmforge-release-0.5.2-2.el6.rf.x86_64.rpm && yum install -y htop")
87 except:
88 pass
89
90 update_config_for_redhat()
91
92def update_config_for_redhat():
93 import re
94
95 global apache_user
96 apache_user = "apache"
97
98 # update memcache user
99 with open("/etc/sysconfig/memcached", "r") as original:
100 memcached_conf = original.read()
101 with open("/etc/sysconfig/memcached", "w") as modified:
102 modified.write(re.sub('USER.*', 'USER="%s"' % apache_user, memcached_conf))
103
104 # set to autostart on startup
105 for service in ("mysqld", "httpd", "memcached", "ntpd"):
106 exec_in_shell("chkconfig --level 2345 %s on" % service)
107 exec_in_shell("service %s restart" % service)
108
109def install_using_apt():
Anand Doshib1b564c2013-07-29 11:44:26 +0530110 exec_in_shell("apt-get update")
Anand Doshie7d77ca2013-08-14 15:12:46 +0530111 packages = "python python-setuptools python-dev build-essential python-pip python-mysqldb apache2 git memcached ntp vim screen htop"
Rushabh Mehta54c15452013-07-16 12:05:41 +0530112 print "-"*80
113 print "Installing Packages: (This may take some time)"
114 print packages
115 print "-"*80
116 exec_in_shell("apt-get install -y %s" % packages)
117
118 if not exec_in_shell("which mysql"):
119 packages = "mysql-server libmysqlclient-dev"
120 print "Installing Packages:", packages
121 exec_in_shell("apt-get install -y %s" % packages)
122
123 update_config_for_debian()
124
125def update_config_for_debian():
126 global apache_user
127 apache_user = "www-data"
128
129 # update memcache user
130 with open("/etc/memcached.conf", "r") as original:
131 memcached_conf = original.read()
132 with open("/etc/memcached.conf", "w") as modified:
133 modified.write(memcached_conf.replace("-u memcache", "-u %s" % apache_user))
134
135 exec_in_shell("a2enmod rewrite")
136
137 for service in ("mysql", "apache2", "memcached", "ntpd"):
138 exec_in_shell("service %s restart" % service)
139
140def install_python_modules():
Anand Doshi08352ca2013-07-17 08:24:50 +0530141 python_modules = "pytz python-dateutil jinja2 markdown2 termcolor python-memcached requests chardet dropbox google-api-python-client pygeoip"
Rushabh Mehta54c15452013-07-16 12:05:41 +0530142
143 print "-"*80
144 print "Installing Python Modules: (This may take some time)"
145 print python_modules
146 print "-"*80
147
Anand Doshie7d77ca2013-08-14 15:12:46 +0530148 if not exec_in_shell("which pip"):
149 exec_in_shell("easy_install pip")
150
151 exec_in_shell("pip install --upgrade pip")
152 exec_in_shell("pip install --upgrade virtualenv")
Rushabh Mehta54c15452013-07-16 12:05:41 +0530153 exec_in_shell("pip install -q %s" % python_modules)
Anand Doshi08352ca2013-07-17 08:24:50 +0530154
Rushabh Mehta54c15452013-07-16 12:05:41 +0530155def install_erpnext(install_path):
156 print
157 print "-"*80
158 print "Installing ERPNext"
159 print "-"*80
160
161 # ask for details
162 global root_password
163 if not root_password:
164 root_password = get_root_password()
165 test_root_connection(root_password)
166
167 db_name = raw_input("ERPNext Database Name: ")
168 if not db_name:
169 raise Exception, "Sorry! You must specify ERPNext Database Name"
170
171 # install folders and conf
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530172 setup_folders(install_path)
Rushabh Mehta54c15452013-07-16 12:05:41 +0530173 setup_conf(install_path, db_name)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530174
175 # setup paths
Rushabh Mehta54c15452013-07-16 12:05:41 +0530176 sys.path.extend([".", "lib", "app"])
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530177
Rushabh Mehta54c15452013-07-16 12:05:41 +0530178 # install database, run patches, update schema
179 setup_db(install_path, root_password, db_name)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530180
Rushabh Mehta54c15452013-07-16 12:05:41 +0530181 setup_cron(install_path)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530182
Rushabh Mehta54c15452013-07-16 12:05:41 +0530183 setup_apache_conf(install_path)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530184
185def get_root_password():
186 # ask for root mysql password
187 import getpass
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530188 root_pwd = None
Rushabh Mehta54c15452013-07-16 12:05:41 +0530189 root_pwd = getpass.getpass("MySQL Root user's Password: ")
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530190 return root_pwd
191
192def test_root_connection(root_pwd):
Rushabh Mehta54c15452013-07-16 12:05:41 +0530193 out = exec_in_shell("mysql -u root %s -e 'exit'" % \
194 (("-p"+root_pwd) if root_pwd else "").replace('$', '\$').replace(' ', '\ '))
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530195 if "access denied" in out.lower():
196 raise Exception("Incorrect MySQL Root user's password")
Rushabh Mehta54c15452013-07-16 12:05:41 +0530197
198def setup_folders(install_path):
Rushabh Mehta54c15452013-07-16 12:05:41 +0530199 app = os.path.join(install_path, "app")
200 if not os.path.exists(app):
201 print "Cloning erpnext"
Anand Doshi08352ca2013-07-17 08:24:50 +0530202 exec_in_shell("cd %s && git clone https://github.com/webnotes/erpnext.git app" % install_path)
Rushabh Mehta54c15452013-07-16 12:05:41 +0530203 exec_in_shell("cd app && git config core.filemode false")
Anand Doshi7d3e75d2013-07-24 19:01:02 +0530204 if not os.path.exists(app):
205 raise Exception, "Couldn't clone erpnext repository"
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530206
Rushabh Mehta54c15452013-07-16 12:05:41 +0530207 lib = os.path.join(install_path, "lib")
208 if not os.path.exists(lib):
209 print "Cloning wnframework"
Anand Doshi08352ca2013-07-17 08:24:50 +0530210 exec_in_shell("cd %s && git clone https://github.com/webnotes/wnframework.git lib" % install_path)
Rushabh Mehta54c15452013-07-16 12:05:41 +0530211 exec_in_shell("cd lib && git config core.filemode false")
Anand Doshi7d3e75d2013-07-24 19:01:02 +0530212 if not os.path.exists(lib):
213 raise Exception, "Couldn't clone wnframework repository"
Rushabh Mehta54c15452013-07-16 12:05:41 +0530214
215 public = os.path.join(install_path, "public")
216 for p in [public, os.path.join(public, "files"), os.path.join(public, "backups"),
217 os.path.join(install_path, "logs")]:
218 if not os.path.exists(p):
219 os.mkdir(p)
220
221def setup_conf(install_path, db_name):
222 import os, string, random, re
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530223
Rushabh Mehta54c15452013-07-16 12:05:41 +0530224 # generate db password
225 char_range = string.ascii_letters + string.digits
226 db_password = "".join((random.choice(char_range) for n in xrange(16)))
227
228 # make conf file
229 with open(os.path.join(install_path, "lib", "conf", "conf.py"), "r") as template:
230 conf = template.read()
231
232 conf = re.sub("db_name.*", 'db_name = "%s"' % (db_name,), conf)
233 conf = re.sub("db_password.*", 'db_password = "%s"' % (db_password,), conf)
234
235 with open(os.path.join(install_path, "conf.py"), "w") as conf_file:
236 conf_file.write(conf)
237
238 return db_password
239
240def setup_db(install_path, root_password, db_name):
Rushabh Mehta54c15452013-07-16 12:05:41 +0530241 from webnotes.install_lib.install import Installer
242 inst = Installer("root", root_password)
Anand Doshi08352ca2013-07-17 08:24:50 +0530243 inst.import_from_db(db_name, verbose=1)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530244
Rushabh Mehta54c15452013-07-16 12:05:41 +0530245 # run patches and sync
Rushabh Mehta54c15452013-07-16 12:05:41 +0530246 exec_in_shell("./lib/wnf.py --patch_sync_build")
247
248def setup_cron(install_path):
Rushabh Mehta54c15452013-07-16 12:05:41 +0530249 erpnext_cron_entries = [
250 "*/3 * * * * cd %s && python lib/wnf.py --run_scheduler >> /var/log/erpnext-sch.log 2>&1" % install_path,
251 "0 */6 * * * cd %s && python lib/wnf.py --backup >> /var/log/erpnext-backup.log 2>&1" % install_path
252 ]
253
254 for row in erpnext_cron_entries:
255 try:
256 existing_cron = exec_in_shell("crontab -l")
257 if row not in existing_cron:
258 exec_in_shell('{ crontab -l; echo "%s"; } | crontab' % row)
259 except:
260 exec_in_shell('echo "%s" | crontab' % row)
261
262def setup_apache_conf(install_path):
263 apache_conf_content = """Listen 8080
264NameVirtualHost *:8080
265<VirtualHost *:8080>
266 ServerName localhost
267 DocumentRoot %s/public/
268
269 AddHandler cgi-script .cgi .xml .py
270 AddType application/vnd.ms-fontobject .eot
271 AddType font/ttf .ttf
272 AddType font/otf .otf
273 AddType application/x-font-woff .woff
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530274
Rushabh Mehta54c15452013-07-16 12:05:41 +0530275 <Directory %s/public/>
276 # directory specific options
277 Options -Indexes +FollowSymLinks +ExecCGI
278
279 # directory's index file
280 DirectoryIndex web.py
281
282 AllowOverride all
283 Order Allow,Deny
284 Allow from all
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530285
Rushabh Mehta54c15452013-07-16 12:05:41 +0530286 # rewrite rule
287 RewriteEngine on
288 RewriteCond %%{REQUEST_FILENAME} !-f
289 RewriteCond %%{REQUEST_FILENAME} !-d
290 RewriteCond %%{REQUEST_FILENAME} !-l
291 RewriteRule ^([^/]+)$ /web.py?page=$1 [QSA,L]
292 </Directory>
293</VirtualHost>""" % (install_path, install_path)
294
295 new_apache_conf_path = os.path.join(install_path, os.path.basename(install_path)+".conf")
296 with open(new_apache_conf_path, "w") as apache_conf_file:
297 apache_conf_file.write(apache_conf_content)
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530298
Rushabh Mehta54c15452013-07-16 12:05:41 +0530299def post_install(install_path):
300 global apache_user
301 exec_in_shell("chown -R %s %s" % (apache_user, install_path))
302
303 apache_conf_filename = os.path.basename(install_path)+".conf"
304 if is_redhat:
305 os.symlink(os.path.join(install_path, apache_conf_filename),
306 os.path.join("/etc/httpd/conf.d", apache_conf_filename))
307 exec_in_shell("service httpd restart")
308
309 elif is_debian:
310 os.symlink(os.path.join(install_path, apache_conf_filename),
311 os.path.join("/etc/apache2/sites-enabled", apache_conf_filename))
312 exec_in_shell("service apache2 restart")
313
314 print
315 print "-"*80
Anand Doshicf2cf382013-08-09 19:39:09 +0530316 print "To change url domain, run: lib/wnf.py --domain example.com"
317 print "-"*80
Rushabh Mehta54c15452013-07-16 12:05:41 +0530318 print "Installation complete"
319 print "Open your browser and go to http://localhost:8080"
320 print "Login using username = Administrator and password = admin"
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530321
Rushabh Mehta54c15452013-07-16 12:05:41 +0530322def exec_in_shell(cmd):
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530323 # using Popen instead of os.system - as recommended by python docs
Rushabh Mehta54c15452013-07-16 12:05:41 +0530324 from subprocess import Popen
325 import tempfile
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530326
Rushabh Mehta54c15452013-07-16 12:05:41 +0530327 with tempfile.TemporaryFile() as stdout:
328 with tempfile.TemporaryFile() as stderr:
329 p = Popen(cmd, shell=True, stdout=stdout, stderr=stderr)
330 p.wait()
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530331
Rushabh Mehta54c15452013-07-16 12:05:41 +0530332 stdout.seek(0)
333 out = stdout.read()
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530334
Rushabh Mehta54c15452013-07-16 12:05:41 +0530335 stderr.seek(0)
336 err = stderr.read()
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530337
Rushabh Mehta54c15452013-07-16 12:05:41 +0530338 if err and any((kw in err.lower() for kw in ["traceback", "error", "exception"])):
339 print out
340 raise Exception, err
341 else:
342 print "."
343
344 return out
345
346if __name__ == "__main__":
Rushabh Mehtaa8f9aa02013-06-21 17:55:43 +0530347 install()