Git based wiki inspired by Gollum
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.

358 lines
8.3KB

  1. from realms import config, app, cli, db
  2. from realms.lib.util import random_string
  3. from subprocess import call, Popen
  4. from multiprocessing import cpu_count
  5. import click
  6. import json
  7. import sys
  8. import os
  9. def check_su(f):
  10. if not in_virtualenv() and not is_su():
  11. # This does not account for people the have user level python installs
  12. # that aren't virtual environments! Should be rare I think
  13. red("This command requires root privileges, use sudo or run as root.")
  14. sys.exit()
  15. return f
  16. def get_user():
  17. for name in ('SUDO_USER', 'LOGNAME', 'USER', 'LNAME', 'USERNAME'):
  18. user = os.environ.get(name)
  19. if user:
  20. return user
  21. def in_virtualenv():
  22. return hasattr(sys, 'real_prefix')
  23. def is_su():
  24. return os.geteuid() == 0
  25. def get_pid():
  26. try:
  27. with file(config.PIDFILE) as f:
  28. pid = f.read().strip()
  29. return pid if pid and int(pid) > 0 and not call(['kill', '-s', '0', pid]) else False
  30. except IOError:
  31. return False
  32. def module_exists(module_name):
  33. try:
  34. __import__(module_name)
  35. except ImportError:
  36. return False
  37. else:
  38. return True
  39. def green(s):
  40. click.secho(s, fg='green')
  41. def yellow(s):
  42. click.secho(s, fg='yellow')
  43. def red(s):
  44. click.secho(s, fg='red')
  45. @cli.command()
  46. @check_su
  47. @click.option('--site-title',
  48. default=config.SITE_TITLE,
  49. prompt='Enter site title.')
  50. @click.option('--base_url',
  51. default=config.BASE_URL,
  52. prompt='Enter base URL.')
  53. @click.option('--port',
  54. default=config.PORT,
  55. prompt='Enter port number.')
  56. @click.option('--secret-key',
  57. default=config.SECRET_KEY if config.SECRET_KEY != "CHANGE_ME" else random_string(64),
  58. prompt='Enter secret key.')
  59. @click.option('--wiki-path',
  60. default=config.WIKI_PATH,
  61. prompt='Enter wiki data directory.',
  62. help='Wiki Directory (git repo)')
  63. @click.option('--allow-anon',
  64. default=config.ALLOW_ANON,
  65. is_flag=True,
  66. prompt='Allow anonymous edits?')
  67. @click.option('--registration-enabled',
  68. default=config.REGISTRATION_ENABLED,
  69. is_flag=True,
  70. prompt='Enable registration?')
  71. @click.option('--cache-type',
  72. default=config.CACHE_TYPE,
  73. type=click.Choice([None, 'simple', 'redis', 'memcached']),
  74. prompt='Cache type?')
  75. @click.option('--db-uri',
  76. default=config.DB_URI,
  77. prompt='Database URI? Examples: http://goo.gl/RyW0cl')
  78. @click.pass_context
  79. def setup(ctx, **kw):
  80. """ Start setup wizard
  81. """
  82. conf = {}
  83. for k, v in kw.items():
  84. conf[k.upper()] = v
  85. conf_path = config.update(conf)
  86. if conf['CACHE_TYPE'] == 'redis':
  87. ctx.invoke(setup_redis)
  88. elif conf['CACHE_TYPE'] == 'memcached':
  89. ctx.invoke(setup_memcached)
  90. green('Config saved to %s' % conf_path)
  91. yellow('Type "realms-wiki start" to start server')
  92. yellow('Type "realms-wiki dev" to start server in development mode')
  93. @click.command()
  94. @click.option('--cache-redis-host',
  95. default=getattr(config, 'CACHE_REDIS_HOST', "127.0.0.1"),
  96. prompt='Redis host')
  97. @click.option('--cache-redis-port',
  98. default=getattr(config, 'CACHE_REDIS_POST', 6379),
  99. prompt='Redis port')
  100. @click.option('--cache-redis-password',
  101. default=getattr(config, 'CACHE_REDIS_PASSWORD', None),
  102. prompt='Redis password')
  103. @click.option('--cache-redis-db',
  104. default=getattr(config, 'CACHE_REDIS_DB', 0),
  105. prompt='Redis db')
  106. def setup_redis(**kw):
  107. conf = {}
  108. for k, v in kw.items():
  109. conf[k.upper()] = v
  110. config.update(conf)
  111. install_redis()
  112. def get_prefix():
  113. return sys.prefix
  114. def get_pip():
  115. """ Get virtualenv path for pip
  116. """
  117. if in_virtualenv():
  118. return get_prefix() + '/bin/pip'
  119. else:
  120. return 'pip'
  121. @cli.command()
  122. @check_su
  123. @click.argument('cmd', nargs=-1)
  124. def pip(cmd):
  125. """ Execute pip commands, useful for virtualenvs
  126. """
  127. call(get_pip() + ' ' + ' '.join(cmd), shell=True)
  128. def install_redis():
  129. call([get_pip(), 'install', 'redis'])
  130. def install_mysql():
  131. call([get_pip(), 'install', 'MySQL-Python'])
  132. def install_postgres():
  133. call([get_pip(), 'install', 'psycopg2'])
  134. def install_memcached():
  135. call([get_pip(), 'install', 'python-memcached'])
  136. @click.command()
  137. @click.option('--cache-memcached-servers',
  138. default=getattr(config, 'CACHE_MEMCACHED_SERVERS', ["127.0.0.1:11211"]),
  139. type=click.STRING,
  140. prompt='Memcached servers, separate with a space')
  141. def setup_memcached(**kw):
  142. conf = {}
  143. for k, v in kw.items():
  144. conf[k.upper()] = v
  145. config.update(conf)
  146. @cli.command()
  147. @check_su
  148. @click.option('--user',
  149. default=get_user(),
  150. type=click.STRING,
  151. prompt='Run as which user? (it must exist)')
  152. @click.option('--port',
  153. default=config.PORT,
  154. type=click.INT,
  155. prompt='What port to listen on?')
  156. @click.option('--workers',
  157. default=cpu_count() * 2 + 1,
  158. type=click.INT,
  159. prompt="Number of workers? (defaults to ncpu*2+1)")
  160. def setup_upstart(**kwargs):
  161. """ Start upstart conf creation wizard
  162. """
  163. from realms.lib.util import upstart_script
  164. if in_virtualenv():
  165. app_dir = get_prefix()
  166. path = '/'.join(sys.executable.split('/')[:-1])
  167. else:
  168. # Assumed root install, not sure if this matters?
  169. app_dir = '/'
  170. path = None
  171. kwargs.update(dict(app_dir=app_dir, path=path))
  172. conf_file = '/etc/init/realms-wiki.conf'
  173. with open('/etc/init/realms-wiki.conf', 'w') as f:
  174. f.write(upstart_script(**kwargs))
  175. green('Wrote file to %s' % conf_file)
  176. green("Type 'sudo start realms-wiki' to start")
  177. green("Type 'sudo stop realms-wiki' to stop")
  178. green("Type 'sudo restart realms-wiki' to restart")
  179. @cli.command()
  180. @click.argument('json_string')
  181. def configure(json_string):
  182. """ Set config.json, expects JSON encoded string
  183. """
  184. try:
  185. config.update(json.loads(json_string))
  186. except ValueError, e:
  187. red('Config value should be valid JSON')
  188. @cli.command()
  189. @click.option('--port', default=5000)
  190. def dev(port):
  191. """ Run development server
  192. """
  193. green("Starting development server")
  194. app.run(host="0.0.0.0",
  195. port=port,
  196. debug=True)
  197. def start_server():
  198. if get_pid():
  199. yellow("Server is already running")
  200. return
  201. flags = '--daemon --pid %s' % config.PIDFILE
  202. green("Server started. Port: %s" % config.PORT)
  203. Popen('gunicorn realms:app -b 0.0.0.0:%s -k gevent %s' %
  204. (config.PORT, flags), shell=True, executable='/bin/bash')
  205. def stop_server():
  206. pid = get_pid()
  207. if not pid:
  208. yellow("Server is not running")
  209. else:
  210. yellow("Shutting down server")
  211. call(['kill', pid])
  212. @cli.command()
  213. def run():
  214. """ Run production server (alias for start)
  215. """
  216. start_server()
  217. @cli.command()
  218. def start():
  219. """ Run server daemon
  220. """
  221. start_server()
  222. @cli.command()
  223. def stop():
  224. """ Stop server
  225. """
  226. stop_server()
  227. @cli.command()
  228. def restart():
  229. """ Restart server
  230. """
  231. stop_server()
  232. start_server()
  233. @cli.command()
  234. def status():
  235. """ Get server status
  236. """
  237. pid = get_pid()
  238. if not pid:
  239. yellow("Server is not running")
  240. else:
  241. green("Server is running PID: %s" % pid)
  242. @cli.command()
  243. def create_db():
  244. """ Creates DB tables
  245. """
  246. green("Creating all tables")
  247. db.create_all()
  248. @cli.command()
  249. @click.confirmation_option(help='Are you sure you want to drop the db?')
  250. def drop_db():
  251. """ Drops DB tables
  252. """
  253. yellow("Dropping all tables")
  254. db.drop_all()
  255. @cli.command()
  256. def test():
  257. """ Run tests
  258. """
  259. for mod in [('flask.ext.testing', 'Flask-Testing'), ('nose', 'nose')]:
  260. if not module_exists(mod[0]):
  261. call([get_pip(), 'install', mod[1]])
  262. nosetests = get_prefix() + "/bin/nosetests" if in_virtualenv() else "nosetests"
  263. call([nosetests, config.APP_PATH])
  264. @cli.command()
  265. def version():
  266. """ Output version
  267. """
  268. with open('VERSION') as f:
  269. return f.read().strip()
  270. if __name__ == '__main__':
  271. cli()