You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

349 line
12 KiB

  1. from __future__ import unicode_literals, absolute_import
  2. import click
  3. import hashlib, os
  4. import frappe
  5. from frappe.commands import pass_context, get_site
  6. from frappe.commands.scheduler import _is_scheduler_enabled
  7. @click.command('new-site')
  8. @click.argument('site')
  9. @click.option('--db-name', help='Database name')
  10. @click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
  11. @click.option('--mariadb-root-password', help='Root password for MariaDB')
  12. @click.option('--admin-password', help='Administrator password for new site', default=None)
  13. @click.option('--verbose', is_flag=True, default=False, help='Verbose')
  14. @click.option('--force', help='Force restore if site/database already exists', is_flag=True, default=False)
  15. @click.option('--source_sql', help='Initiate database with a SQL file')
  16. @click.option('--install-app', multiple=True, help='Install app after installation')
  17. def new_site(site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None, force=None, install_app=None, db_name=None):
  18. "Create a new site"
  19. if not db_name:
  20. db_name = hashlib.sha1(site).hexdigest()[:10]
  21. frappe.init(site=site, new_site=True)
  22. _new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=verbose, install_apps=install_app, source_sql=source_sql, force=force)
  23. if len(frappe.utils.get_sites()) == 1:
  24. use(site)
  25. def _new_site(db_name, site, mariadb_root_username=None, mariadb_root_password=None, admin_password=None, verbose=False, install_apps=None, source_sql=None,force=False, reinstall=False):
  26. "Install a new Frappe site"
  27. from frappe.installer import install_db, make_site_dirs
  28. from frappe.installer import install_app as _install_app
  29. import frappe.utils.scheduler
  30. frappe.init(site=site)
  31. try:
  32. # enable scheduler post install?
  33. enable_scheduler = _is_scheduler_enabled()
  34. except:
  35. enable_scheduler = False
  36. install_db(root_login=mariadb_root_username, root_password=mariadb_root_password, db_name=db_name, admin_password=admin_password, verbose=verbose, source_sql=source_sql,force=force, reinstall=reinstall)
  37. make_site_dirs()
  38. _install_app("frappe", verbose=verbose, set_as_patched=not source_sql)
  39. if frappe.conf.get("install_apps"):
  40. for app in frappe.conf.install_apps:
  41. _install_app(app, verbose=verbose, set_as_patched=not source_sql)
  42. if install_apps:
  43. for app in install_apps:
  44. _install_app(app, verbose=verbose, set_as_patched=not source_sql)
  45. frappe.utils.scheduler.toggle_scheduler(enable_scheduler)
  46. frappe.db.commit()
  47. scheduler_status = "disabled" if frappe.utils.scheduler.is_scheduler_disabled() else "enabled"
  48. print "*** Scheduler is", scheduler_status, "***"
  49. frappe.destroy()
  50. @click.command('restore')
  51. @click.argument('sql-file-path')
  52. @click.option('--mariadb-root-username', default='root', help='Root username for MariaDB')
  53. @click.option('--mariadb-root-password', help='Root password for MariaDB')
  54. @click.option('--db-name', help='Database name for site in case it is a new one')
  55. @click.option('--admin-password', help='Administrator password for new site')
  56. @click.option('--install-app', multiple=True, help='Install app after installation')
  57. @click.option('--with-public-files', help='Restores the public files of the site, given path to its tar file')
  58. @click.option('--with-private-files', help='Restores the private files of the site, given path to its tar file')
  59. @pass_context
  60. def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_password=None, db_name=None, verbose=None, install_app=None, admin_password=None, force=None, with_public_files=None, with_private_files=None):
  61. "Restore site database from an sql file"
  62. from frappe.installer import extract_sql_gzip, extract_tar_files
  63. # Extract the gzip file if user has passed *.sql.gz file instead of *.sql file
  64. if sql_file_path.endswith('sql.gz'):
  65. sql_file_path = extract_sql_gzip(os.path.abspath(sql_file_path))
  66. site = get_site(context)
  67. frappe.init(site=site)
  68. db_name = db_name or frappe.conf.db_name or hashlib.sha1(site).hexdigest()[:10]
  69. _new_site(db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, verbose=context.verbose, install_apps=install_app, source_sql=sql_file_path, force=context.force)
  70. # Extract public and/or private files to the restored site, if user has given the path
  71. if with_public_files:
  72. extract_tar_files(site, with_public_files, 'public')
  73. if with_private_files:
  74. extract_tar_files(site, with_private_files, 'private')
  75. @click.command('reinstall')
  76. @pass_context
  77. def reinstall(context):
  78. "Reinstall site ie. wipe all data and start over"
  79. site = get_site(context)
  80. try:
  81. frappe.init(site=site)
  82. frappe.connect()
  83. frappe.clear_cache()
  84. installed = frappe.get_installed_apps()
  85. frappe.clear_cache()
  86. except Exception:
  87. installed = []
  88. finally:
  89. if frappe.db:
  90. frappe.db.close()
  91. frappe.destroy()
  92. frappe.init(site=site)
  93. _new_site(frappe.conf.db_name, site, verbose=context.verbose, force=True, reinstall=True, install_apps=installed)
  94. @click.command('install-app')
  95. @click.argument('app')
  96. @pass_context
  97. def install_app(context, app):
  98. "Install a new app to site"
  99. from frappe.installer import install_app as _install_app
  100. for site in context.sites:
  101. frappe.init(site=site)
  102. frappe.connect()
  103. try:
  104. _install_app(app, verbose=context.verbose)
  105. finally:
  106. frappe.destroy()
  107. @click.command('list-apps')
  108. @pass_context
  109. def list_apps(context):
  110. "List apps in site"
  111. site = get_site(context)
  112. frappe.init(site=site)
  113. frappe.connect()
  114. print "\n".join(frappe.get_installed_apps())
  115. frappe.destroy()
  116. @click.command('add-system-manager')
  117. @click.argument('email')
  118. @click.option('--first-name')
  119. @click.option('--last-name')
  120. @pass_context
  121. def add_system_manager(context, email, first_name, last_name):
  122. "Add a new system manager to a site"
  123. import frappe.utils.user
  124. for site in context.sites:
  125. frappe.connect(site=site)
  126. try:
  127. frappe.utils.user.add_system_manager(email, first_name, last_name)
  128. frappe.db.commit()
  129. finally:
  130. frappe.destroy()
  131. @click.command('migrate')
  132. @click.option('--rebuild-website', help="Rebuild webpages after migration")
  133. @pass_context
  134. def migrate(context, rebuild_website=False):
  135. "Run patches, sync schema and rebuild files/translations"
  136. from frappe.migrate import migrate
  137. for site in context.sites:
  138. print 'Migrating', site
  139. frappe.init(site=site)
  140. frappe.connect()
  141. try:
  142. migrate(context.verbose, rebuild_website=rebuild_website)
  143. finally:
  144. frappe.destroy()
  145. @click.command('run-patch')
  146. @click.argument('module')
  147. @pass_context
  148. def run_patch(context, module):
  149. "Run a particular patch"
  150. import frappe.modules.patch_handler
  151. for site in context.sites:
  152. frappe.init(site=site)
  153. try:
  154. frappe.connect()
  155. frappe.modules.patch_handler.run_single(module, force=context.force)
  156. finally:
  157. frappe.destroy()
  158. @click.command('reload-doc')
  159. @click.argument('module')
  160. @click.argument('doctype')
  161. @click.argument('docname')
  162. @pass_context
  163. def reload_doc(context, module, doctype, docname):
  164. "Reload schema for a DocType"
  165. for site in context.sites:
  166. try:
  167. frappe.init(site=site)
  168. frappe.connect()
  169. frappe.reload_doc(module, doctype, docname, force=context.force)
  170. frappe.db.commit()
  171. finally:
  172. frappe.destroy()
  173. @click.command('use')
  174. @click.argument('site')
  175. def _use(site, sites_path='.'):
  176. "Set a default site"
  177. use(site, sites_path=sites_path)
  178. def use(site, sites_path='.'):
  179. with open(os.path.join(sites_path, "currentsite.txt"), "w") as sitefile:
  180. sitefile.write(site)
  181. @click.command('backup')
  182. @click.option('--with-files', default=False, is_flag=True, help="Take backup with files")
  183. @pass_context
  184. def backup(context, with_files=False, backup_path_db=None, backup_path_files=None,
  185. backup_path_private_files=None, quiet=False):
  186. "Backup"
  187. from frappe.utils.backups import scheduled_backup
  188. verbose = context.verbose
  189. for site in context.sites:
  190. frappe.init(site=site)
  191. frappe.connect()
  192. odb = scheduled_backup(ignore_files=not with_files, backup_path_db=backup_path_db, backup_path_files=backup_path_files, backup_path_private_files=backup_path_private_files, force=True)
  193. if verbose:
  194. from frappe.utils import now
  195. print "database backup taken -", odb.backup_path_db, "- on", now()
  196. if with_files:
  197. print "files backup taken -", odb.backup_path_files, "- on", now()
  198. print "private files backup taken -", odb.backup_path_private_files, "- on", now()
  199. frappe.destroy()
  200. @click.command('remove-from-installed-apps')
  201. @click.argument('app')
  202. @pass_context
  203. def remove_from_installed_apps(context, app):
  204. "Remove app from site's installed-apps list"
  205. from frappe.installer import remove_from_installed_apps
  206. for site in context.sites:
  207. try:
  208. frappe.init(site=site)
  209. frappe.connect()
  210. remove_from_installed_apps(app)
  211. finally:
  212. frappe.destroy()
  213. @click.command('uninstall-app')
  214. @click.argument('app')
  215. @click.option('--dry-run', help='List all doctypes that will be deleted', is_flag=True, default=False)
  216. @pass_context
  217. def uninstall(context, app, dry_run=False):
  218. "Remove app and linked modules from site"
  219. from frappe.installer import remove_app
  220. for site in context.sites:
  221. try:
  222. frappe.init(site=site)
  223. frappe.connect()
  224. remove_app(app, dry_run)
  225. finally:
  226. frappe.destroy()
  227. @click.command('drop-site')
  228. @click.argument('site')
  229. @click.option('--root-login', default='root')
  230. @click.option('--root-password')
  231. @click.option('--archived-sites-path')
  232. def drop_site(site, root_login='root', root_password=None, archived_sites_path=None):
  233. "Remove site from database and filesystem"
  234. from frappe.installer import get_current_host, make_connection
  235. from frappe.model.db_schema import DbManager
  236. from frappe.utils.backups import scheduled_backup
  237. frappe.init(site=site)
  238. frappe.connect()
  239. scheduled_backup(ignore_files=False, force=True)
  240. db_name = frappe.local.conf.db_name
  241. frappe.local.db = make_connection(root_login, root_password)
  242. dbman = DbManager(frappe.local.db)
  243. dbman.delete_user(db_name, get_current_host())
  244. dbman.drop_database(db_name)
  245. if not archived_sites_path:
  246. archived_sites_path = os.path.join(frappe.get_app_path('frappe'), '..', '..', '..', 'archived_sites')
  247. if not os.path.exists(archived_sites_path):
  248. os.mkdir(archived_sites_path)
  249. move(archived_sites_path, site)
  250. def move(dest_dir, site):
  251. import os
  252. if not os.path.isdir(dest_dir):
  253. raise Exception, "destination is not a directory or does not exist"
  254. frappe.init(site)
  255. old_path = frappe.utils.get_site_path()
  256. new_path = os.path.join(dest_dir, site)
  257. # check if site dump of same name already exists
  258. site_dump_exists = True
  259. count = 0
  260. while site_dump_exists:
  261. final_new_path = new_path + (count and str(count) or "")
  262. site_dump_exists = os.path.exists(final_new_path)
  263. count = int(count or 0) + 1
  264. os.rename(old_path, final_new_path)
  265. frappe.destroy()
  266. return final_new_path
  267. @click.command('set-admin-password')
  268. @click.argument('admin-password')
  269. @pass_context
  270. def set_admin_password(context, admin_password):
  271. "Set Administrator password for a site"
  272. import getpass
  273. for site in context.sites:
  274. try:
  275. frappe.init(site=site)
  276. while not admin_password:
  277. admin_password = getpass.getpass("Administrator's password for {0}: ".format(site))
  278. frappe.connect()
  279. frappe.db.sql("""update __Auth set `password`=password(%s)
  280. where user='Administrator'""", (admin_password,))
  281. frappe.db.commit()
  282. admin_password = None
  283. finally:
  284. frappe.destroy()
  285. commands = [
  286. add_system_manager,
  287. backup,
  288. drop_site,
  289. install_app,
  290. list_apps,
  291. migrate,
  292. new_site,
  293. reinstall,
  294. reload_doc,
  295. remove_from_installed_apps,
  296. restore,
  297. run_patch,
  298. set_admin_password,
  299. uninstall,
  300. _use,
  301. ]